http://www.example.org/abcdef
”。可以使用“”代替其他“
abcdef
”字符串包含a-z, A-Z and 0-9
的六个字符。这使56〜570亿个可能的字符串成为可能。我的方法:
我有一个包含三列的数据库表:
id ,整数,自动递增
长字符串,用户输入的长URL
然后我将插入将长网址插入表格。然后,我将为“
id
”选择自动增量值,并为其构建一个哈希值。然后应将此哈希插入为“ short
”。但是我应该建立什么样的哈希?像MD5这样的哈希算法创建的字符串太长。我认为我不使用这些算法。自建算法也将起作用。我的想法:
对于“
http://www.google.de/
”,我得到了自动递增的ID 239472
。然后,我执行以下步骤:short = '';
if divisible by 2, add "a"+the result to short
if divisible by 3, add "b"+the result to short
... until I have divisors for a-z and A-Z.
可以重复进行直到该数字不再可除。您认为这是一个好方法吗?您有更好的主意吗?
由于对该主题的持续关注,我为GitHub发布了一个有效的解决方案,其中包含JavaScript,PHP,Python和Java的实现。如果您愿意,请添加您的解决方案:)
#1 楼
我将继续您的“将数字转换为字符串”方法。但是,您将意识到,如果ID为质数且大于52,则建议的算法将失败。理论背景
您需要双射函数f。这是必要的,以便为f(123)='abc'函数找到反函数g('abc')= 123。这意味着:
必须没有x1,x2(x1≠x2)会使f(x1)= f(x2),
并且对于每个y必须能够找到一个x,以便f(x)= y。
如何将ID转换为缩短的URL
想想字母,我们想要使用。您的情况是
[a-zA-Z0-9]
。它包含62个字母。使用自动生成的唯一数字键(例如,MySQL表的自动递增的
id
)。对于此示例,我将使用12510(125的底数为10)。
现在,您必须将12510转换为X62(底数为62)。
12510 = 2×621 + 1×620 =
[2,1]
这需要使用整数除法和取模。一个伪代码示例:
digits = []
while num > 0
remainder = modulo(num, 62)
digits.push(remainder)
num = divide(num, 62)
digits = digits.reverse
现在将索引2和1映射到您的字母。这就是您的映射(例如带有数组)的样子:
0 → a
1 → b
...
25 → z
...
52 → 0
61 → 9
使用2→c和1→b,您将收到cb62作为缩短的URL 。
http://shor.ty/cb
如何将缩短的URL解析为初始ID
反之则容易得多。您只需对字母进行反向查找。
e9a62将被解析为“字母表中的第4、61和0个字母”。
e9a62 =
[4,61,0]
= 4×622 + 61×621 + 0×620 = 1915810 现在用
WHERE id = 19158
查找数据库记录并进行重定向。示例实现(由评论者提供)
C ++
Python
Haskell
C#
CoffeeScript
Perl
评论
别忘了清理恶意JavaScript代码的URL!请记住,JavaScript可以在URL中进行base64编码,因此仅搜索'javascript'是不够的。j
–比约恩
09年4月14日在8:05
函数必须是双射的(内射和外射的)才能具有逆函数。
–浓汤
2010年5月4日在20:28
值得深思的是,在URL中添加两个字符的校验和可能很有用。这样可以防止系统中所有URL的直接迭代。简单的东西,例如f(checksum(id)%(62 ^ 2))+ f(id)= url_id
–koblas
10-9-4 '13:53
就清理URL而言,垃圾邮件发送者使用您的服务掩盖其URL以避免垃圾邮件过滤器是您要面对的问题之一。您需要将服务限制在已知好的参与者身上,或者将垃圾邮件过滤应用于长网址。否则,您将被垃圾邮件发送者滥用。
–爱德华·福克
13年5月26日在15:34
Base62可能是一个错误的选择,因为它有可能生成f *个单词(例如3792586 =='F_ck',其中u代替_)。我会排除一些字符,例如u / U,以将其最小化。
– Paulo Scardine
13年6月28日在16:02
#2 楼
为什么要使用哈希?您只需将自动增量值转换为字母数字值即可。通过使用一些基本转换,您可以轻松地做到这一点。假设字符空间(A-Z,a-z,0-9等)有62个字符,将id转换为以40为底的数字,然后将这些字符用作数字。
评论
从A-Z,a-z和0-9 = 62个字符而不是40个字符这一事实来看,您就对了。
–埃文·特兰(Evan Teran)
09年4月12日在16:39
谢谢!那我应该使用62底的字母吗? en.wikipedia.org/wiki/Base_62但是如何将ID转换为以62为基数的数字?
–牛
09年4月12日在16:46
使用课程的基本转换算法-en.wikipedia.org/wiki/Base_conversion#Change_of_radix
–shoosh
09年4月12日在16:48
关于“为什么要使用哈希?”,基于自动增量的基本转换将创建顺序的URL,因此您需要让人们能够“浏览”其他人的缩短URL,对?
–安德鲁·科尔森(Andrew Coleson)
09年4月12日在18:05
只要有足够的资源和时间,您就可以“浏览”任何URL缩短服务的所有URL。
–shoosh
09年4月12日在21:10
#3 楼
public class UrlShortener {
private static final String ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
private static final int BASE = ALPHABET.length();
public static String encode(int num) {
StringBuilder sb = new StringBuilder();
while ( num > 0 ) {
sb.append( ALPHABET.charAt( num % BASE ) );
num /= BASE;
}
return sb.reverse().toString();
}
public static int decode(String str) {
int num = 0;
for ( int i = 0; i < str.length(); i++ )
num = num * BASE + ALPHABET.indexOf(str.charAt(i));
return num;
}
}
评论
我真的很喜欢这个主意,我唯一的问题是我不断使解码函数中的num变量超出范围(即使很长时间),您是否知道如何使它起作用?还是仅仅是理论上的?
–user1322801
16年5月12日在19:07
@ user1322801:大概您正在尝试对远大于编码功能实际处理能力的内容进行解码。如果将所有“ int”都转换为BigInteger,则可以得到更多的里程,但是除非您有> 922337203685477575807索引,否则长的时间就足够了。
–biggusjimmus
16 Jul 19'6:08
我可以知道换向的重要性是什么?即sb.reverse()。toString();
– dotNet解码器
2017年6月7日2:00
那62 ^ 62 = 1.7万亿?
–诺亚·托尼(Noah Tony)
18年8月24日在19:30
#4 楼
不能回答您的问题,但是我不会使用区分大小写的简短URL。它们很难记住,通常难以理解(许多字体呈现1和l,0和O以及其他字符非常相似,以至于几乎无法分辨出它们之间的差异)并且容易产生彻头彻尾的错误。尝试仅使用小写或大写字母。此外,尝试使用以预定义形式混合数字和字符的格式。有研究表明,人们会比其他人更好地记住一种形式(想想电话号码,其中的电话以特定形式分组)。试试像num-char-char-num-char-char这样的东西。我知道这会降低组合,特别是在没有大写和小写的情况下,但这会更有用,因此很有用。
评论
谢谢,很好的主意。我还没想过显然,是否有意义取决于使用的类型。
–牛
09年4月12日在18:22
如果人们严格复制并粘贴短网址,这将不是问题。
–爱德华·福克
13年5月26日在15:35
短网址的目的不是令人难忘或不容易说出来。仅单击或复制/粘贴。
–Hugo Nogueira
16 Dec 5'在14:12
是的,我认为短URL仅用于人们列出或通过电子邮件发送,因此它很短,不会像某些URL那样占用200个字符,因此大小写不是问题
–nonopolarity
19/12/10在7:46
#5 楼
我的方法:获取数据库ID,然后对Base36进行编码。我不会同时使用大写和小写字母,因为这会使通过电话传输这些URL成为噩梦,但是您当然可以轻松地将该功能扩展为以62为基础的en / decoder。评论
谢谢,你是对的。无论您拥有2,176,782,336的可能性还是56,800,235,584的可能性,都相同:两者都足够。因此,我将使用base 36编码。
–牛
09年4月14日在18:22
可能显而易见,但这是Wikipedia中引用的一些PHP代码,用于在php中进行base64编码tonymarston.net/php-mysql/converter.html
–瑞安·怀特(Ryan White)
10年7月13日在15:33
#6 楼
这是我的PHP 5类。<?php
class Bijective
{
public $dictionary = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
public function __construct()
{
$this->dictionary = str_split($this->dictionary);
}
public function encode($i)
{
if ($i == 0)
return $this->dictionary[0];
$result = '';
$base = count($this->dictionary);
while ($i > 0)
{
$result[] = $this->dictionary[($i % $base)];
$i = floor($i / $base);
}
$result = array_reverse($result);
return join("", $result);
}
public function decode($input)
{
$i = 0;
$base = count($this->dictionary);
$input = str_split($input);
foreach($input as $char)
{
$pos = array_search($char, $this->dictionary);
$i = $i * $base + $pos;
}
return $i;
}
}
#7 楼
Node.js和MongoDB解决方案由于我们知道MongoDB用于创建具有12个字节的新ObjectId的格式。
一个4字节的值表示自Unix时代以来的秒数,
一个3字节的机器标识符,
一个2字节的进程ID
一个3字节的计数器(在您的计算机中),从一个随机值开始。 br />
示例(我选择一个随机序列)
a1b2c3d4e5f6g7h8i9j1k2l3
a1b2c3d4代表自Unix纪元以来的秒数,
4e5f6g7代表机器标识符,
h8i9代表进程ID
j1k2l3代表计数器,从随机值开始。
因为如果将数据存储在同一台机器上,计数器将是唯一的
因此,短URL将成为计数器,这是一个代码段,假定您的服务器运行正常。
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
// Create a schema
const shortUrl = new Schema({
long_url: { type: String, required: true },
short_url: { type: String, required: true, unique: true },
});
const ShortUrl = mongoose.model('ShortUrl', shortUrl);
// The user can request to get a short URL by providing a long URL using a form
app.post('/shorten', function(req ,res){
// Create a new shortUrl */
// The submit form has an input with longURL as its name attribute.
const longUrl = req.body["longURL"];
const newUrl = ShortUrl({
long_url : longUrl,
short_url : "",
});
const shortUrl = newUrl._id.toString().slice(-6);
newUrl.short_url = shortUrl;
console.log(newUrl);
newUrl.save(function(err){
console.log("the new URL is added");
})
});
评论
RDBMS会比无SQL /键值存储更好吗?
– kjs3
6月6日17:16
@ kjs3是的,您是对的,因为与其他表没有关系,所以不需要RDBMS,并且键值存储将更快。
–Firas Omrane
6月7日22:34
#8 楼
我会不断增加数据库中每个域的整数序列,并使用Hashids将整数编码为URL路径。static hashids = Hashids(salt = "my app rocks", minSize = 6)
我运行了一个脚本来查看需要多长时间直到耗尽字符长度为止。对于六个字符,它可以执行
164,916,224
链接,然后最多七个字符。位使用七个字符。不足五个字符对我来说很奇怪。Hashids可以将URL路径解码回整数,但更简单的解决方案是将整个短链接
sho.rt/ka8ds3
用作主键。完整的概念在这里:
function addDomain(domain) {
table("domains").insert("domain", domain, "seq", 0)
}
function addURL(domain, longURL) {
seq = table("domains").where("domain = ?", domain).increment("seq")
shortURL = domain + "/" + hashids.encode(seq)
table("links").insert("short", shortURL, "long", longURL)
return shortURL
}
// GET /:hashcode
function handleRequest(req, res) {
shortURL = req.host + "/" + req.param("hashcode")
longURL = table("links").where("short = ?", shortURL).get("long")
res.redirect(301, longURL)
}
#9 楼
C#版本:public class UrlShortener
{
private static String ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
private static int BASE = 62;
public static String encode(int num)
{
StringBuilder sb = new StringBuilder();
while ( num > 0 )
{
sb.Append( ALPHABET[( num % BASE )] );
num /= BASE;
}
StringBuilder builder = new StringBuilder();
for (int i = sb.Length - 1; i >= 0; i--)
{
builder.Append(sb[i]);
}
return builder.ToString();
}
public static int decode(String str)
{
int num = 0;
for ( int i = 0, len = str.Length; i < len; i++ )
{
num = num * BASE + ALPHABET.IndexOf( str[(i)] );
}
return num;
}
}
#10 楼
您可以对整个URL进行哈希处理,但是如果您只是想缩短ID,请按照建议的方式进行操作。我编写了以下Python实现:https://gist.github.com/778542
#11 楼
看看https://hashids.org/,它是开源的,并且有多种语言。他们的页面概述了其他方法的一些缺陷。
#12 楼
如果您不想重新发明轮子... http://lilurl.sourceforge.net/评论
“很抱歉,垃圾邮件发送者似乎已经知道了。改为尝试tinyurl。”
–takeshin
2010-1-31在17:24
到演示站点。仍可从Sourceforge下载源代码。
– Alister Bulman
2010-2-12在22:02
#13 楼
// simple approach
$original_id = 56789;
$shortened_id = base_convert($original_id, 10, 36);
$un_shortened_id = base_convert($shortened_id, 36, 10);
#14 楼
alphabet = map(chr, range(97,123)+range(65,91)) + map(str,range(0,10))
def lookup(k, a=alphabet):
if type(k) == int:
return a[k]
elif type(k) == str:
return a.index(k)
def encode(i, a=alphabet):
'''Takes an integer and returns it in the given base with mappings for upper/lower case letters and numbers 0-9.'''
try:
i = int(i)
except Exception:
raise TypeError("Input must be an integer.")
def incode(i=i, p=1, a=a):
# Here to protect p.
if i <= 61:
return lookup(i)
else:
pval = pow(62,p)
nval = i/pval
remainder = i % pval
if nval <= 61:
return lookup(nval) + incode(i % pval)
else:
return incode(i, p+1)
return incode()
def decode(s, a=alphabet):
'''Takes a base 62 string in our alphabet and returns it in base10.'''
try:
s = str(s)
except Exception:
raise TypeError("Input must be a string.")
return sum([lookup(i) * pow(62,p) for p,i in enumerate(list(reversed(s)))])a
这是我需要的人的版本。
#15 楼
为什么不将您的ID转换为字符串?您只需要一个将0到61之间的数字映射到单个字母(大写/小写)或数字的函数。然后应用它来创建4个字母的代码,您将覆盖1470万个URL。评论
+1为简单化思维。真的就是这么简单。我刚刚发布了一个答案,正是这个答案。我有一些用于查询数据库的生产代码,以确保没有重复的字符串并且所有内容都是唯一的。
–安德鲁·里斯(Andrew Reese)
19年11月14日在18:10
#16 楼
这是PHP的一个不错的URL编码功能...// From http://snipplr.com/view/22246/base62-encode--decode/
private function base_encode($val, $base=62, $chars='0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ') {
$str = '';
do {
$i = fmod($val, $base);
$str = $chars[$i] . $str;
$val = ($val - $i) / $base;
} while($val > 0);
return $str;
}
#17 楼
不知道是否有人会发现它有用-它更像是一种“ hack n slash”方法,但简单易行,如果只需要特定的字符,效果很好。$dictionary = "abcdfghjklmnpqrstvwxyz23456789";
$dictionary = str_split($dictionary);
// Encode
$str_id = '';
$base = count($dictionary);
while($id > 0) {
$rem = $id % $base;
$id = ($id - $rem) / $base;
$str_id .= $dictionary[$rem];
}
// Decode
$id_ar = str_split($str_id);
$id = 0;
for($i = count($id_ar); $i > 0; $i--) {
$id += array_search($id_ar[$i-1], $dictionary) * pow($base, $i - 1);
}
#18 楼
您是否故意省略了O,0和i?我只是基于Ryan的解决方案创建了一个PHP类。
<?php
$shorty = new App_Shorty();
echo 'ID: ' . 1000;
echo '<br/> Short link: ' . $shorty->encode(1000);
echo '<br/> Decoded Short Link: ' . $shorty->decode($shorty->encode(1000));
/**
* A nice shorting class based on Ryan Charmley's suggestion see the link on Stack Overflow below.
* @author Svetoslav Marinov (Slavi) | http://WebWeb.ca
* @see http://stackoverflow.com/questions/742013/how-to-code-a-url-shortener/10386945#10386945
*/
class App_Shorty {
/**
* Explicitly omitted: i, o, 1, 0 because they are confusing. Also use only lowercase ... as
* dictating this over the phone might be tough.
* @var string
*/
private $dictionary = "abcdfghjklmnpqrstvwxyz23456789";
private $dictionary_array = array();
public function __construct() {
$this->dictionary_array = str_split($this->dictionary);
}
/**
* Gets ID and converts it into a string.
* @param int $id
*/
public function encode($id) {
$str_id = '';
$base = count($this->dictionary_array);
while ($id > 0) {
$rem = $id % $base;
$id = ($id - $rem) / $base;
$str_id .= $this->dictionary_array[$rem];
}
return $str_id;
}
/**
* Converts /abc into an integer ID
* @param string
* @return int $id
*/
public function decode($str_id) {
$id = 0;
$id_ar = str_split($str_id);
$base = count($this->dictionary_array);
for ($i = count($id_ar); $i > 0; $i--) {
$id += array_search($id_ar[$i - 1], $this->dictionary_array) * pow($base, $i - 1);
}
return $id;
}
}
?>
评论
是。您在类声明的下面看到了注释吗?
– Svetoslav Marinov
2015年11月5日在10:08
#19 楼
public class TinyUrl {
private final String characterMap = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
private final int charBase = characterMap.length();
public String covertToCharacter(int num){
StringBuilder sb = new StringBuilder();
while (num > 0){
sb.append(characterMap.charAt(num % charBase));
num /= charBase;
}
return sb.reverse().toString();
}
public int covertToInteger(String str){
int num = 0;
for(int i = 0 ; i< str.length(); i++)
num += characterMap.indexOf(str.charAt(i)) * Math.pow(charBase , (str.length() - (i + 1)));
return num;
}
}
class TinyUrlTest{
public static void main(String[] args) {
TinyUrl tinyUrl = new TinyUrl();
int num = 122312215;
String url = tinyUrl.covertToCharacter(num);
System.out.println("Tiny url: " + url);
System.out.println("Id: " + tinyUrl.covertToInteger(url));
}
}
#20 楼
这就是我使用的方法:# Generate a [0-9a-zA-Z] string
ALPHABET = map(str,range(0, 10)) + map(chr, range(97, 123) + range(65, 91))
def encode_id(id_number, alphabet=ALPHABET):
"""Convert an integer to a string."""
if id_number == 0:
return alphabet[0]
alphabet_len = len(alphabet) # Cache
result = ''
while id_number > 0:
id_number, mod = divmod(id_number, alphabet_len)
result = alphabet[mod] + result
return result
def decode_id(id_string, alphabet=ALPHABET):
"""Convert a string to an integer."""
alphabet_len = len(alphabet) # Cache
return sum([alphabet.index(char) * pow(alphabet_len, power) for power, char in enumerate(reversed(id_string))])
它非常快并且可以使用长整数。
#21 楼
对于类似的项目,要获取新的密钥,我将围绕随机字符串生成器创建包装函数,该生成器将调用该生成器,直到得到哈希表中尚未使用的字符串。一旦您的名称空间开始变满,此方法就会变慢,但是正如您已经说过的,即使只有6个字符,您也可以使用很多命名空间。评论
从长远来看,这种方法对您有用吗?
–克里斯
16年5月10日在13:40
老实说,我不知道我在那指的是哪个项目:-P
–乔尔·伯格(Joel Berger)
16年5月10日在16:34
#22 楼
我有一个问题的变体,因为我存储了许多不同作者的网页,并且需要防止通过猜测来发现网页。因此,我的短网址在页码的Base-62字符串中添加了几个额外的数字。这些多余的数字是从页面记录本身的信息中生成的,它们确保3844个URL中只有1个有效(假定2个数字为Base-62)。您可以在http://mgscan.com/MBWL上看到概述说明。#23 楼
很好的答案,我创建了bjf的Golang实现:package bjf
import (
"math"
"strings"
"strconv"
)
const alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
func Encode(num string) string {
n, _ := strconv.ParseUint(num, 10, 64)
t := make([]byte, 0)
/* Special case */
if n == 0 {
return string(alphabet[0])
}
/* Map */
for n > 0 {
r := n % uint64(len(alphabet))
t = append(t, alphabet[r])
n = n / uint64(len(alphabet))
}
/* Reverse */
for i, j := 0, len(t) - 1; i < j; i, j = i + 1, j - 1 {
t[i], t[j] = t[j], t[i]
}
return string(t)
}
func Decode(token string) int {
r := int(0)
p := float64(len(token)) - 1
for i := 0; i < len(token); i++ {
r += strings.Index(alphabet, string(token[i])) * int(math.Pow(float64(len(alphabet)), p))
p--
}
return r
}
托管在github:https://github.com/xor-gate/go-bjf
#24 楼
在Scala中实现:class Encoder(alphabet: String) extends (Long => String) {
val Base = alphabet.size
override def apply(number: Long) = {
def encode(current: Long): List[Int] = {
if (current == 0) Nil
else (current % Base).toInt :: encode(current / Base)
}
encode(number).reverse
.map(current => alphabet.charAt(current)).mkString
}
}
class Decoder(alphabet: String) extends (String => Long) {
val Base = alphabet.size
override def apply(string: String) = {
def decode(current: Long, encodedPart: String): Long = {
if (encodedPart.size == 0) current
else decode(current * Base + alphabet.indexOf(encodedPart.head),encodedPart.tail)
}
decode(0,string)
}
}
Scala测试示例:
import org.scalatest.{FlatSpec, Matchers}
class DecoderAndEncoderTest extends FlatSpec with Matchers {
val Alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
"A number with base 10" should "be correctly encoded into base 62 string" in {
val encoder = new Encoder(Alphabet)
encoder(127) should be ("cd")
encoder(543513414) should be ("KWGPy")
}
"A base 62 string" should "be correctly decoded into a number with base 10" in {
val decoder = new Decoder(Alphabet)
decoder("cd") should be (127)
decoder("KWGPy") should be (543513414)
}
}
#25 楼
基于Xeoncross类的函数function shortly($input){
$dictionary = ['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','0','1','2','3','4','5','6','7','8','9'];
if($input===0)
return $dictionary[0];
$base = count($dictionary);
if(is_numeric($input)){
$result = [];
while($input > 0){
$result[] = $dictionary[($input % $base)];
$input = floor($input / $base);
}
return join("", array_reverse($result));
}
$i = 0;
$input = str_split($input);
foreach($input as $char){
$pos = array_search($char, $dictionary);
$i = $i * $base + $pos;
}
return $i;
}
#26 楼
这是一个可能是bit.ly的Node.js实现。生成一个高度随机的七个字符的字符串。它使用Node.js加密算法生成高度随机的25个字符集,而不是随机选择七个字符。
var crypto = require("crypto");
exports.shortURL = new function () {
this.getShortURL = function () {
var sURL = '',
_rand = crypto.randomBytes(25).toString('hex'),
_base = _rand.length;
for (var i = 0; i < 7; i++)
sURL += _rand.charAt(Math.floor(Math.random() * _rand.length));
return sURL;
};
}
评论
您所说的“ bit.ly”是什么意思?
– Peter Mortensen
18-10-17在18:47
#27 楼
我的Python 3版本base_list = list("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
base = len(base_list)
def encode(num: int):
result = []
if num == 0:
result.append(base_list[0])
while num > 0:
result.append(base_list[num % base])
num //= base
print("".join(reversed(result)))
def decode(code: str):
num = 0
code_list = list(code)
for index, code in enumerate(reversed(code_list)):
num += base_list.index(code) * base ** index
print(num)
if __name__ == '__main__':
encode(341413134141)
decode("60FoItT")
#28 楼
有关优质的Node.js / JavaScript解决方案,请参阅id-shortener模块,该模块经过了全面测试,并且已经在生产中使用了几个月。它提供了可插拔存储支持的高效id / URL缩短器默认为Redis,您甚至可以自定义短ID字符集以及缩短是否是幂等的。这是一个重要的区别,并非所有URL缩短器都考虑在内。
相对于此处的其他答案,此模块实现了Marcel Jackwerth上面公认的出色答案。
核心以下Redis Lua片段提供了解决方案:
local sequence = redis.call('incr', KEYS[1])
local chars = '0123456789ABCDEFGHJKLMNPQRSTUVWXYZ_abcdefghijkmnopqrstuvwxyz'
local remaining = sequence
local slug = ''
while (remaining > 0) do
local d = (remaining % 60)
local character = string.sub(chars, d + 1, d + 1)
slug = character .. slug
remaining = (remaining - d) / 60
end
redis.call('hset', KEYS[2], slug, ARGV[1])
return slug
#29 楼
为什么不只生成一个随机字符串并将其附加到基本URL?这是用C#进行的非常简化的版本。static string chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
static string baseUrl = "https://google.com/";
private static string RandomString(int length)
{
char[] s = new char[length];
Random rnd = new Random();
for (int x = 0; x < length; x++)
{
s[x] = chars[rnd.Next(chars.Length)];
}
Thread.Sleep(10);
return new String(s);
}
然后只需将随机字符串附加到baseURL:
string tinyURL = baseUrl + RandomString(5);
记住这是一个这样做的简化版本,并且RandomString方法可能会创建重复的字符串。在生产中,您需要考虑重复的字符串,以确保您始终拥有唯一的URL。我有一些代码通过查询我可以共享的数据库表来考虑重复的字符串,如果有人感兴趣的话。
#30 楼
这是我最初的想法,可以做更多的思考,或者可以进行一些模拟,以查看它是否有效或需要任何改进:我的答案是记住数据库中的长URL。 ,并使用ID
0
到9999999999999999
(或需要很大的数字)。但是ID 0到
9999999999999999
可能是一个问题,因为如果我们使用十六进制,甚至使用base62或base64,它可以更短。 (就像YouTube使用
A
-Z
a
-z
0
-9
和_
一样)。如果从
-
到0
统一增加,那么黑客可以按此顺序访问它们并知道人们相互发送的URL,因此这可能是一个隐私问题我们可以这样做:
有一台服务器将
9999999999999999
分配给0
到一台服务器A,所以现在是服务器A有1000个这样的ID。因此,如果有20或200台服务器不断需要新的ID,则不必继续要求每个新ID,而只需一次请求1000个ID 作为ID 1,例如,将这些位反转即可。因此,
999
变为000...00000001
,因此当转换为base64时,每次将是非均匀增加的ID。使用XOR翻转最终ID的位。例如,与
10000...000
(例如密钥)进行XOR,并且某些位将被翻转。 (只要密钥的1位打开,它将翻转ID的位)。这将使ID更加难以猜测,并且显得更加随机按照此方案,分配ID的单个服务器可以形成ID,而请求分配ID的20或200个服务器也可以形成ID。分配服务器必须使用锁/信号量,以防止两个发出请求的服务器获得相同的批处理(或者如果它一次接受一个连接,则已经解决了问题)。因此,我们不想等待等待分配的行(队列)太长。这就是为什么一次分配1000或10000可以解决此问题的原因。
评论
@gudge这些函数的要点是它们具有逆函数。这意味着您可以同时具有encode()和decode()函数。因此,步骤如下:(1)将URL保存在数据库中(2)从数据库中获取该URL的唯一行ID(3)将整数ID转换为带有encode()的短字符串,例如273984至f5a4(4)在可共享的URL中使用短字符串(例如f4a4)(5)收到短字符串(例如20a8)的请求时,请使用decode()将字符串解码为整数ID(6)查找给定ID的数据库中的URL。要进行转换,请使用:github.com/delight-im/ShortURL@Marco,将散列存储在数据库中有什么意义?
@MaksimVi。如果您具有可逆函数,则没有任何函数。如果您具有单向哈希函数,则将具有一个。
如果我们使用简单的CRC32算法来缩短URL,那会错吗?尽管不太可能发生冲突(CRC32输出通常为8个字符长,这给我们带来了超过3000万的可能性)如果生成的CRC32输出先前已被使用并在数据库中找到,我们可以使用随机数加长网址直到找到在我的数据库中唯一的CRC32输出。对于一个简单的解决方案,这将是多么糟糕,不同或丑陋?
Java中典型的数字到短字符串的转换方法