生成易于阅读和使用的短且唯一ID

131
  • 每天需要处理 > 1000 但 < 10000 条新记录

  • 不能使用 GUID/UUIDs,自增数字等

  • 最好是 5 或 6 个字符长,当然可以是字母

  • 如果有现成的知名算法可以重用最好

有什么适合的吗?


为什么不使用自增的 INT 或 BIGINT 呢?这可能是最易读且可以轻松处理数据量的选项。 - Malk
根据上面的问题,尝试将其保持在5/6个字符以内,并支持每天多达9999条新记录。 - Kumar
@Kumar - 如果一天需要处理超过9999条记录怎么办?你提出的解决方案似乎不可行。 - ChaosPandion
@ChaosPandion:我认为这些可能只是负载/流量的粗略猜测,而不是硬性限制。我不确定为什么你想在每日交易数量上设置任意限制。 - Paul Sasik
你可以将它编码为base64并使用。我不确定你能否将其缩小到比这更小并仍然使用可读字符。但我会认为,与base32相比,base64要不可读得多,因为它需要向大多数字符添加额外的限定符(大写F,小写O,小写O与只是F,OO)。 - Malk
6个回答

174

Base 62是tinyurl和bit.ly用于缩短URL的方法。它是一种为创建“唯一”的、可读性强的ID而被广泛使用的方法。当然,在创建ID时,您需要存储已创建的ID并检查重复项以确保唯一性。(请参见答案底部的代码)

Base 62唯一性度量

5个字符在Base 62中将为您提供62^5个唯一ID = 916,132,832(约1十亿) 每天10k个ID,您将可以使用91k+天

6个字符在Base 62中将为您提供62^6个唯一ID = 56,800,235,584(56+十亿) 每天10k个ID,您将可以使用5+百万天

Base 36唯一性度量

6个字符将为您提供36^6个唯一ID = 2,176,782,336(2十亿+) 7个字符将为您提供36^7个唯一ID = 78,364,164,096(78+十亿)

代码:

public void TestRandomIdGenerator()
{
    // create five IDs of six, base 62 characters
    for (int i=0; i<5; i++) Console.WriteLine(RandomIdGenerator.GetBase62(6));

    // create five IDs of eight base 36 characters
    for (int i=0; i<5; i++) Console.WriteLine(RandomIdGenerator.GetBase36(8));
}

public static class RandomIdGenerator 
{
    private static char[] _base62chars = 
        "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
        .ToCharArray();

    private static Random _random = new Random();

    public static string GetBase62(int length) 
    {
        var sb = new StringBuilder(length);

        for (int i=0; i<length; i++) 
            sb.Append(_base62chars[_random.Next(62)]);

        return sb.ToString();
    }       

    public static string GetBase36(int length) 
    {
        var sb = new StringBuilder(length);

        for (int i=0; i<length; i++) 
            sb.Append(_base62chars[_random.Next(36)]);

        return sb.ToString();
    }
}

输出:

z5KyMg
wd4SUp
uSzQtH
UPrGAT
UIf2IS
QCF9GNM5 0UV3TFSS 3MG91VKP 7NTRF10T AJK3AJU7

3
看起来很棒,有任何不区分大小写的选项吗? - Kumar
3
如果您想避免大小写敏感,可以使用 36 进制:http://www.codeproject.com/Articles/10619/Base-36-type-for-NET-C,但要像基于 62 进制的 ID 那样获得那么多的排列组合,您需要在您的 ID 中使用更多字符。这是一个权衡。或者您可以尝试使用除了字母之外的其他字符,但这对用户来说会很难看。 - Paul Sasik
26
去除元音字母可能有助于避免在公共场合意外生成脏话。这是一个想法,请注意保持翻译内容的通俗易懂和原意不变。 - Damien Sawyer
11
根据使用场景(特别是如果需要人类读取和重新输入代码),您可能需要考虑排除常常混淆的字符:0/O 和 I/l/1。在某些情况下,可以通过选择良好的字体来缓解这种情况,但是从问题描述中无法确定提问者是否拥有此控制权。 - GrandOpener
3
随着我们创建更多的ID,数据库检查将会发现越来越多的重复项,导致性能变慢。因此,这种方法在长期内无法扩展。 - Arun Avanathan
显示剩余8条评论

26

我推荐使用http://hashids.org/,它可以将任何数字(例如DB ID)转换为字符串(使用盐)。

该工具允许解码该字符串以返回原来的数字。因此您无需在数据库中存储它。

该工具具有JavaScript、Ruby、Python、Java、Scala、PHP、Perl、Swift、Clojure、Objective-C、C、C++11、Go、Erlang、Lua、Elixir、ColdFusion、Groovy、Kotlin、Nim、VBA、CoffeeScript版本以及Node.js和.NET的库。


1
你能提供类似你提议的其他选项吗?这很有趣。我想知道PostgreSQL中是否有任何默认选项。 - Léo Léopold Hertz 준영
1
这是 .NET 的版本,但你能否解释一下它如何工作,而无需将其存储在数据库中?我能否生成仅具有唯一性的随机数,而不需要输入数字和盐? - Shaiju T
@Slawa 我需要类似于.NET的Hashids,但最终的哈希值将存储在具有固定长度的列中,是否可以说始终生成最大长度为N的哈希值? - Anon Dev

8
我有与OP相似的要求。我调查了可用的库,但它们大多基于随机性,而我不想要那样的东西。我真的找不到任何不基于随机性且仍非常短的内容...所以最终我根据Flickr使用的技术自己开发了一种方法,但进行了修改以减少协调,并允许长时间离线。

简而言之:

  • 中央服务器发出由32个ID组成的ID块
  • 本地ID生成器维护一个ID块池,以便在请求时生成ID。当池变得不足时,它会从服务器获取更多的ID块来重新填充。

缺点:

  • 需要中央协调
  • ID或多或少是可预测的(比常规数据库ID更少,但它们并不是随机的)

优点:

  • 保持在53位以内(Javascript / PHP整数数字的最大值)
  • 非常短的ID
  • 使用Base 36编码,因此对于人类来说很容易阅读、书写和发音
  • 在需要再次与服务器联系之前,可以在本地生成ID很长一段时间(取决于池设置)
  • 理论上没有碰撞的机会

我已经发布了一个用于客户端的Javascript库,以及一个Java EE服务器实现。在其他语言中实现服务器也应该很容易。

以下是这些项目:

suid - 分布式服务唯一ID,简短而甜美

suid-server-java - Java EE技术栈的Suid-server实现。

这两个库都可在宽松的知识共享开源许可下获得。 希望这可以帮助其他寻找短唯一ID的人。


你能否将 https://dev59.com/2mkw5IYBdhLWcg3w8fBJ#29372036 与你的建议“suid”进行比较? - Léo Léopold Hertz 준영
2
它基于随机数。实际上,它非常棒。但是你的ID可能不会像它们本来可以那样短。我编写了SUID以从1开始编号,因此您将从极短的ID开始。想想3或4个字符。此外,具有(大致)递增排序的ID还具有其他一些好处,除了从真正短的ID开始。 - Stijn de Witt

4

我曾在几年前为一款应用程序解决问题时使用了36进制。我需要生成一个人类可读且相对唯一的号码(在当前日历年度内)。我选择使用从当前年份1月1日午夜开始到现在的毫秒数(每年的时间戳会重复)并将其转换为36进制的数字。如果正在开发的系统遇到致命问题,它将生成一个7个字符的36进制数字,并通过Web界面显示给最终用户,然后最终用户可以将遇到的问题(和数字)传达给技术支持人员(这样技术支持人员可以使用该数字找到日志中堆栈跟踪开始的地方)。对于用户来说,像56af42g7这样的号码比像2016-01-21T15:34:29.933-08: 00或随机UUID 5f0d3e0c-da96-11e5-b5d2-0a1d41d68578更容易阅读和传达。


4
能否请您提供一个详细的伪代码,展示一下您的提案?听起来很有趣。 - Léo Léopold Hertz 준영

1
我会建议尝试使用SnowflakeIDs... 它们的内存消耗比UUIDs要少。

1
我非常喜欢只使用Base64格式对GUID进行编码并截断尾部的==以获得一个22个字符的字符串的简单性(这只需要一行代码,而且您始终可以将其转换回GUID)。 不幸的是,它有时会包含+和/字符。在数据库中可以,但对于URL来说不太好,但它帮助我欣赏其他答案 :-)
来源:Christiaan van Bergen的https://www.codeproject.com/Tips/1236704/Reducing-the-string-Length-of-a-Guid
我们发现将Guid(16字节)转换为ASCII表示形式的Base64会导致仅有22个字符的可用且仍然唯一的messageID。
var newGuid = Guid.NewGuid();
var messageID = Convert.ToBase64String(newGuid.ToByteArray());

var message22chars = Convert.ToBase64String(Guid.NewGuid().ToByteArray()).Substring(0,22);

例如:Guid 'e6248889-2a12-405a-b06d-9695b82c0a9c'(字符串长度为36)将获得Base64表示形式:'iYgk5hIqWkCwbZaVuCwKnA=='(字符串长度为24)。Base64表示以'=='字符结尾。您可以截断这些字符,而不会影响唯一性。留下一个长度仅为22个字符的标识符。

遗憾的是,有时会包含+和/字符,因此请改用Base64Url变体(RFC 4648)。 - Ian Goldby
@IanGoldby Base62没有那个“问题”。 - Bouke
@Bouke 你似乎没有理解我的观点。上面的答案有时会生成包含"/"和"+"的ID(已经承认)。最简单的解决方案是使用URL安全的base64变体-大多数库都可以轻松获得,因此毫不费力而且没有任何缺点。既然知道这一点,为什么还要实现自己的62个字符字母表(这也会生成稍微更长的ID)呢? - Ian Goldby
1
@IanGoldby 如果唯一的目标是 URL 安全,那么 RFC 4648 是完美的。Base64 编码也更好,因为编码更简单,长度更容易理解。我不喜欢 Base64 的地方是 + / 或 - _ 是词边界,并且使用 Base62 可以更容易选择文本,尽管这需要更复杂的编码方案。 - Bouke

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接