关于算法:如何创建URL缩短器?

How do I create a URL shortener?

我想创建一个URL缩短器服务,您可以在其中将一个长的URL写入输入字段,该服务将URL缩短为"http://www.example.org/abcdef"。

除"abcdef外,还可以有任何其他包含a-z, A-Z and 0-9的六个字符的字符串。这使得56~57亿个弦成为可能。

我的方法是:

我有一个数据库表,有三列:

  • ID,整数,自动递增
  • long,string,用户输入的长URL
  • 短、字符串、缩短的URL(或仅六个字符)
  • 然后我将把长URL插入到表中。然后我将为"id"选择自动增量值并构建一个散列值。然后应将该哈希插入为"short"。但是我应该建立什么样的哈希表呢?像MD5这样的哈希算法创建的字符串太长。我想我不使用这些算法。一个自建的算法也会起作用。

    我的想法是:

    对于"http://www.google.de/"我得到了自动递增ID 239472。然后我执行以下步骤:

    1
    2
    3
    4
    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.

    这可以重复,直到数字不再可除。你认为这是个好方法吗?你有更好的主意吗?

    Due to the ongoing interest in this topic, I've published an efficient solution to GitHub, with implementations for JavaScript, PHP, Python and Java. Add your solutions if you like :)


    我将继续您的"将数字转换为字符串"方法。但是,如果您的ID是一个素数并且大于52,那么您将认识到您的建议算法失败。

    理论背景

    您需要一个双目标函数f。这是必需的,这样您就可以为f(123)=abc函数找到一个反函数g("abc")=123。这意味着:

    • 必须没有使f(x1)=f(x2)的x1,x2(x1≠x2),
    • 对于每一个y,你必须能找到一个x,这样f(x)=y。

    如何将ID转换为缩短的URL

  • 想想我们要用的字母表。在你的例子中,这是[a-zA-Z0-9]。它包含62个字母。
  • 取一个自动生成的唯一数字键(例如mysql表的自动递增id)。

    对于本例,我将使用12510(125 with a base of 10).

  • 现在您必须将12510转换为x62(base 62)。

    12510=2×621+1×620=[2,1]

    这需要使用整数除法和模。伪代码示例:

    1
    2
    3
    4
    5
    6
    7
    8
    digits = []

    while num > 0
      remainder = modulo(num, 62)
      digits.push(remainder)
      num = divide(num, 62)

    digits = digits.reverse

    现在将索引2和1映射到字母表中。这就是您的映射(例如数组)的外观:

    1
    2
    3
    4
    5
    6
    7
    0  → a
    1  → b
    ...
    25 → z
    ...
    52 → 0
    61 → 9

    使用2→c和1→b,您将收到c b62as the shorted url.

    1
    http://shor.ty/cb
  • 如何将缩短的URL解析为初始ID

    反过来更容易。你只要用字母表做一个反向查找。

  • e9a62将解析为"字母表中的第4、61和0个字母"。

    e9a62=[4,61,0]=4×622+61×621+0×620=1915810

  • 现在用WHERE id = 19158找到数据库记录,并进行重定向。

  • 一些实现(由注释者提供)

    • 红宝石
    • Python
    • 咖啡剧本
    • 哈斯克尔
    • 珀尔
    • C.*


    为什么要使用哈希?

    您可以简单地将自动递增值转换为字母数字值。通过使用一些基本转换,可以很容易地做到这一点。假设字符空间(a-z、a-z、0-9等)有40个字符,将ID转换为以40为基数的数字,并将字符用作数字。


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    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;
        }  
    }


    不是您问题的答案,但我不会使用区分大小写的缩写URL。它们很难记住,通常不可读(许多字体呈现1和L、0和O以及其他非常相似的字符,几乎不可能分辨出区别),并且容易出现完全正确的错误。尽量只使用小写或大写。

    另外,请尝试使用一种格式,将数字和字符以预定义的形式混合在一起。有研究表明,人们比其他人更容易记住一种形式(想想电话号码,电话号码按特定形式分组)。尝试类似num char char num char char的方法。我知道这会降低组合,特别是如果你没有大小写,但它会更有用,因此也更有用。


    我的方法:获取数据库ID,然后base36对其进行编码。我不会同时使用大写和小写字母,因为这会使通过电话传输这些URL成为一场噩梦,但您当然可以轻松地将该功能扩展为一个基本的62 en/解码器。


    这是我的php5类。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    <?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;
        }
    }

    node.js和mongodb解决方案

    因为我们知道MongoDB使用的格式来创建一个12字节的新objectid。

    • 表示自Unix epoch以来的秒数的4字节值,
    • 一个3字节的机器标识符,
    • 2字节进程ID
    • 一个3字节的计数器(在您的机器中),从一个随机值开始。

    示例(我选择一个随机序列)A1B2C3D4E5F6G7H8I9J1K2L3

    • a1b2c3d4表示自Unix时代以来的秒数,
    • 4E5F6G7表示机器标识符,
    • H8I9表示进程ID
    • j1k2l3表示计数器,从随机值开始。

    因为如果我们将数据存储在同一台机器中,计数器将是唯一的,所以我们可以毫无疑问地得到它是重复的。

    因此,短URL将是计数器,这里是一个代码片段,假设您的服务器运行正常。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    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");
        })
    });

    您可以散列整个URL,但如果您只是想缩短ID,请按照Marcel的建议执行。我编写了这个python实现:

    https://gist.github.com/778542


    C版本:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    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;
        }  
    }


    1
    2
    3
    4
    5
    6
    7
    // simple approach

    $original_id = 56789;

    $shortened_id = base_convert($original_id, 10, 36);

    $un_shortened_id = base_convert($shortened_id, 36, 10);


    我不断增加数据库中每个域的整数序列,并使用哈希ID将整数编码为URL路径。

    1
    static hashids = Hashids(salt ="my app rocks", minSize = 6)

    我运行了一个脚本,看看它需要多长时间,直到耗尽字符长度。对于6个字符,它可以执行164,916,224链接,然后最多可执行7个字符。位使用七个字符。在我看来,不到五个字符看起来很奇怪。

    hashid可以将URL路径解码回整数,但更简单的解决方案是使用整个短链接sho.rt/ka8ds3作为主键。

    以下是完整的概念:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    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)
    }

    如果你不想重新发明轮子…http://lilurl.sourceforge.net网站/


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    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

    这是我的版本,适合任何需要它的人。


    你故意省略了O,0和我吗?

    我刚刚根据Ryan的解决方案创建了一个PHP类。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    <?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;
            }
        }
    ?>


    不知道是否有人会发现这一点有用——它更像是一个"hack n slash"方法,但是如果您只需要特定的字符,它很简单并且工作得很好。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    $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);
    }

    下面是一个不错的PHP URL编码函数…

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 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;
    }

    Xeoncross类中的函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    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;
    }

    我有一个不同的问题,因为我存储了许多不同作者的网页,需要通过猜测来防止发现网页。因此,我的短URL为页码的base-62字符串添加了几个额外的数字。这些额外的数字是由页记录本身的信息生成的,它们确保3844个URL中只有1个有效(假设2位数的base-62)。您可以在http://mgscan.com/mbwl上看到大纲说明。


    对于类似的项目,为了获得一个新的键,我在一个调用生成器的随机字符串生成器周围创建了一个包装函数,直到得到一个尚未在哈希表中使用的字符串为止。一旦您的名称空间开始满了,这个方法就会变慢,但是正如您所说,即使只有6个字符,您也有足够的名称空间可以使用。


    这是我使用的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    # 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))])

    它非常快,可以用长整数。


    很好的答案,我创建了一个bjf的golang实现:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    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


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    /**
     * <p>

     *     Integer to character and vice-versa
     *
    </p>
     *  
     */
    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));
        }
    }

    我的python 3版本

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    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")

    这里有一个node.js实现,它可能会bit.ly。生成高度随机的七个字符串。

    它使用node.js crypto生成高度随机的25个字符集,而不是随机选择7个字符。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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;
        };
    }


    有关quality node.js/javascript解决方案,请参阅ID shortener模块,该模块经过了全面测试,已在生产中使用数月。

    它提供了一个高效的ID/URL缩短器,由默认为Redis的可插入存储支持,您甚至可以自定义短ID字符集,以及缩短是否是等量的。这是一个重要的区别,并不是所有的URL缩短器都要考虑到。

    关于这里的其他答案,本模块实现了马塞尔·杰克沃思的上述优秀的公认答案。

    解决方案的核心由以下redis-lua片段提供:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    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

    在scala中实现:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    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测试的测试示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    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)
      }

    }

    为什么不把你的ID翻译成字符串呢?您只需要一个函数将介于0和61之间的数字映射到单个字母(大写/小写)或数字。然后应用这个来创建,比如说,4个字母的代码,你就有1470万个URL被覆盖了。