跳至内容

ULID生成器 - 在线免费创建唯一可排序ID

免费的在线ULID生成器,使用Crockford Base32编码规则,创建长度固定为26个字符、按字典顺序可直接排序的唯一标识符。生成的每个ID都携带毫秒级时间戳信息,可读性优于随机UUID,非常适合用作数据库主键,以及分布式系统和API接口中需要按时间顺序排列的资源标识符。

ULID生成器

请输入数值以查看结果
加载计算器...
📚

文档

什么是 ULID?

ULID(通用唯一的字典序可排序标识符)是一种包含 26 个字符的代码,可用于标记数据库中的记录或分布式系统中的事件。它将当前时间与随机数据结合起来,因此按字母顺序对 ULID 列表排序后,结果也会按照每个 ULID 的创建时间排序。ULID 生成器就是用于生成这类代码的工具。

ULID 的结构

ULID 由两个部分组成,中间没有分隔符,依次写在一起:

  • 时间戳(10 个字符):自 1970 年 1 月 1 日(Unix 纪元)以来经过的毫秒数,最多占用 48 位。
  • 随机部分(16 个字符):80 位随机数据,用于确保同一毫秒内创建的两个 ULID 几乎肯定不同。

两个部分都使用 Crockford Base32 字母表表示:数字 0-9 和字母 A-Z,但不包含 I、L、O、U 四个字母。省略这四个字母,是因为它们可能与数字 0、1 或彼此混淆。最终得到的字符串长度较短,便于朗读,也可以直接安全地放入 URL。

ULID 的计算方式

ULID 生成器分三个步骤构建标识符。

  1. 读取当前时间,单位为自 Unix 纪元以来的毫秒数。这是一个 48 位数字。
  2. 生成 80 位随机数据,通常使用加密安全的随机数生成器,从而得到 10 个随机字节。
  3. 分别编码两个部分,使用 Crockford Base32 字母表:48 位时间戳变为 10 个字符,80 位随机值变为 16 个字符。将这两个编码后的字符串连接起来,就得到最终的 26 字符 ULID。

编码时每次读取 5 位,因为每个 Base32 字符表示 32 种可能值中的一种(2^5 = 32)。10 个字符最多可容纳 50 位,足以表示 48 位时间戳。16 个字符正好容纳 80 位,与随机部分完全匹配,不会剩余位。

计算示例

假设 ULID 生成器在时间戳 1712345678901(自纪元以来的毫秒数)运行,并生成随机字节 [12, 240, 88, 3, 199, 45, 6, 231, 128, 17]。

步骤 1——编码时间戳。 反复将 1712345678901 除以 32,每次取余数,再从最后一个余数读到第一个余数(这是标准的进制转换方法)。结果是 10 字符字符串 01HTQW311N。

步骤 2——编码随机部分。 将 10 个随机字节组合成一个 80 位数据流,然后每次读取 5 位,并将每个 5 位分组转换为一个 Base32 字符。结果是 16 字符字符串 1KR5G0Y75M3EF00H。

步骤 3——连接两个部分。 最终的 ULID 为:

101HTQW311N 1KR5G0Y75M3EF00H
2

去掉空格后写作:01HTQW311N1KR5G0Y75M3EF00H,总长度为 26 个字符。

使用此生成器

生成器在浏览器中运行。每个 ULID 都由浏览器时钟和浏览器加密随机数生成器生成的 80 位全新随机数据构成,因此不会向服务器发送或从服务器获取任何标识符。

  • ULID 数量接受 1 到 50 之间的整数。默认值为 1。
  • 生成 ULID会生成一批新的 ULID。更改数量也会生成一批新的 ULID。
  • 结果中的复制会复制对应的单个 ULID。显示多个 ULID 时,全部复制会复制整个列表,每行一个 ULID。
  • 重置会将数量恢复为 1,并生成一个新的 ULID。

结果下方的“ULID 结构”面板会将该批次中的第一个 ULID 拆分为两个部分:前 10 个字符(时间戳)和后 16 个字符(随机部分)。

ULID 与 UUID 的比较

特性ULIDUUID(版本 4)
长度26 个字符36 个字符(包含连字符)
可按创建时间排序是否
编码方式Crockford Base32十六进制
包含时间戳是(48 位)否
无需转义即可用于 URL是是

这两种格式都旨在生成跨系统唯一的标识符,无需由中央机构统一分配编号。两者在实际使用中的主要区别是排序方式:ULID 以时间戳开头,因此直接按纯文本对 ULID 排序时,也会按时间排序。随机 UUID 不具备这一特性,因为其中的每一部分都是随机的。

常见用途

  • 数据库主键:ULID 允许数据库按照大致的插入顺序为新行建立索引,许多数据库引擎处理这种索引时比处理完全随机的键更高效。
  • 分布式系统:不同服务器可以独立生成 ULID,无需与中央计数器协调,生成结果仍几乎肯定是唯一的。
  • 事件日志:由于 ULID 按时间排序,事件 ID 列表也可以作为粗略的时间线。
  • API 和文件标识符:固定的 26 字符、适合 URL 的格式可以方便地用于网址和文件名。

相关的标识符格式还包括 KSUID,它同样会编码时间戳以支持排序;以及 Snowflake ID,由 Twitter 等机构使用,将时间戳、机器标识符和计数器组合在一起。

代码示例

下面的每个示例都按照 ULID 规范所描述的方式构建 ULID:将 48 位时间戳编码为 10 个 Crockford Base32 字符,将 80 位随机数据编码为另外 16 个字符,然后将两部分连接起来。

JavaScript

1const ENCODING_CHARS = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
2
3function encodeULID(time, randomBytes) {
4  // Timestamp: 48 bits -> 10 characters
5  let timestampStr = '';
6  let t = time;
7  for (let i = 0; i < 10; i++) {
8    timestampStr = ENCODING_CHARS[t % 32] + timestampStr;
9    t = Math.floor(t / 32);
10  }
11
12  // Randomness: 80 bits -> 16 characters
13  let result = timestampStr;
14  let bits = 0;
15  let bitCount = 0;
16  for (let i = 0; i < 10; i++) {
17    bits = (bits << 8) | randomBytes[i];
18    bitCount += 8;
19    while (bitCount >= 5) {
20      bitCount -= 5;
21      result += ENCODING_CHARS[(bits >> bitCount) & 0x1f];
22    }
23  }
24  return result;
25}
26
27function generateULID() {
28  const randomBytes = crypto.getRandomValues(new Uint8Array(10));
29  return encodeULID(Date.now(), randomBytes);
30}
31
32console.log(generateULID());
33

Python

1import os
2import time
3
4ENCODING_CHARS = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"
5
6def encode_ulid(time_ms, random_bytes):
7    # Timestamp: 48 bits -> 10 characters
8    chars = []
9    t = time_ms
10    for _ in range(10):
11        chars.append(ENCODING_CHARS[t % 32])
12        t //= 32
13    timestamp_part = "".join(reversed(chars))
14
15    # Randomness: 80 bits -> 16 characters
16    bits = 0
17    bit_count = 0
18    random_part = []
19    for byte in random_bytes:
20        bits = (bits << 8) | byte
21        bit_count += 8
22        while bit_count >= 5:
23            bit_count -= 5
24            random_part.append(ENCODING_CHARS[(bits >> bit_count) & 0x1F])
25
26    return timestamp_part + "".join(random_part)
27
28def generate_ulid():
29    return encode_ulid(int(time.time() * 1000), os.urandom(10))
30
31print(generate_ulid())
32

Java

1import java.security.SecureRandom;
2import java.time.Instant;
3
4public class ULIDGenerator {
5    private static final SecureRandom random = new SecureRandom();
6    private static final char[] ENCODING_CHARS =
7        "0123456789ABCDEFGHJKMNPQRSTVWXYZ".toCharArray();
8
9    public static String generateULID() {
10        long timestamp = Instant.now().toEpochMilli();
11        byte[] randomBytes = new byte[10];
12        random.nextBytes(randomBytes);
13
14        // Timestamp: 48 bits -> 10 characters
15        char[] timestampChars = new char[10];
16        long t = timestamp;
17        for (int i = 9; i >= 0; i--) {
18            timestampChars[i] = ENCODING_CHARS[(int) (t % 32)];
19            t /= 32;
20        }
21
22        // Randomness: 80 bits -> 16 characters
23        StringBuilder result = new StringBuilder(new String(timestampChars));
24        long bits = 0;
25        int bitCount = 0;
26        for (byte b : randomBytes) {
27            bits = (bits << 8) | (b & 0xFF);
28            bitCount += 8;
29            while (bitCount >= 5) {
30                bitCount -= 5;
31                result.append(ENCODING_CHARS[(int) ((bits >> bitCount) & 0x1F)]);
32            }
33        }
34        return result.toString();
35    }
36
37    public static void main(String[] args) {
38        System.out.println(generateULID());
39    }
40}
41

常见问题

ULID 用于什么?
ULID 可用作数据库行、API 资源或日志事件的唯一标识符,适用于同时需要按照创建时间排序的场景。

ULID 有多长?
ULID 的长度始终为 26 个字符:时间戳占 10 个字符,随机部分占 16 个字符。

ULID 使用什么编码?
ULID 使用 Crockford Base32 字母表,由数字 0-9 和字母 A-Z 组成,但移除了 I、L、O、U,以避免与其他字符混淆。

ULID 与 UUID 有什么区别?
ULID 会按照创建时间排序,长度为 26 个字符。随机 UUID(版本 4)不会按时间排序,包含连字符在内的长度为 36 个字符。

两个 ULID 发生冲突的可能性有多大?
80 位随机数据使发生冲突的可能性极低。在同一毫秒内生成大约 1.3 万亿个 ULID,才会达到任意两个 ULID 相同的概率为 50%。

没有互联网连接时能否生成 ULID?
可以。ULID 只需要本地时钟和随机数生成器,因此可以完全离线创建。

参考资料

  1. “ULID 规范。”GitHub,https://github.com/ulid/spec。
  2. “Crockford Base32 编码。”http://www.crockford.com/base32.html。