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 生成器分三个步骤构建标识符。
- 读取当前时间,单位为自 Unix 纪元以来的毫秒数。这是一个 48 位数字。
- 生成 80 位随机数据,通常使用加密安全的随机数生成器,从而得到 10 个随机字节。
- 分别编码两个部分,使用 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 的比较
| 特性 | ULID | UUID(版本 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());
33Python
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())
32Java
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 只需要本地时钟和随机数生成器,因此可以完全离线创建。
参考资料
- “ULID 规范。”GitHub,https://github.com/ulid/spec。
- “Crockford Base32 编码。”http://www.crockford.com/base32.html。