使用方法
- 打开页面就有一组完整结果:种子是 nano、长度 21、数量 5、字母表为 NanoID 默认的 64 个字符,结果区已经列出 5 个 ID,不需要先填任何东西。
- 想换一批就改「随机种子」:任意文字都行,同一个种子配上同一套长度与字母表,永远得到同一串 ID,方便把结果贴进文档、测试用例或工单之后原样复现。
- 「ID 长度」默认 21,可填 1 到 256;「生成数量」默认 5,可填 1 到 1000。三个输入框任意一个按回车都会立即重新生成。
- 「自定义字母表」默认填的就是 A 到 Z、a 到 z、0 到 9 外加短横与下划线的 64 个字符。改成十六进制的 16 个字符会明显降熵,摘要里的熵与碰撞数字会同步变小。
- 点「立即生成」或改动任一输入框都会重算;结果区那一行是本次的字母表大小、熵、组合数与碰撞 1% 所需个数,是当场算出来的,不是固定文案。
- 结果区下面是 ID 列表,一行一个;「复制结果」复制这份纯文本,可直接粘进代码或表格。
- 下方「长度与字母表对照表」共 14 行,默认显示 12 行,点「加载更多」看余下 2 行;「导出 CSV」保存为带字节序标记的文件,「复制对照表」复制制表符分隔文本。
- 本页不联网、不写本地存储,刷新即回到默认示例。
这个NanoID生成器能解决什么
需要短 ID 的地方比想象中多:短链后缀、订单号、一次性令牌、前端列表的 key、日志里的追踪标识。直接用自增数字会暴露业务量,用时间戳又容易撞车,这个NanoID生成器把这件事拆成两半:一半是按字母表与长度生成定长 ID,另一半是把这组参数的强度算给你看。
第一类是「要能复现」的场合。写单元测试、构造演示数据、给缺陷单附一份样例时,最烦的是每次刷新都不一样、断言没法写。本页按「种子推进确定性序列」的方式生成,种子与参数不变,五个 ID 逐字不变。
第二类是「要评估强度」的场合。长度 8 与长度 21 的差距不是 13 个字符,而是 48 bits 与 126 bits 的差距,后者的取法数是前者的 2 的 78 次方倍。把长度与字母表大小输进去,摘要直接给出组合数与「达到 1% 碰撞概率需要生成多少个」,不用自己按计算器也不必记公式。
边界必须写清:真实的 NanoID 用的是 crypto.getRandomValues 这类密码学安全随机源,本页为了让「同种子出同结果」改用确定性算法,因此这里生成的 ID 不能用于生产环境的密钥、会话标识、密码重置令牌等安全场景,要用于生产请直接调用官方库。
熵、字母表与确定性生成的原理
一个 ID 的熵等于每一位能提供的随机性之和。字母表大小为 A、长度为 L 时,熵 = L × log₂A,组合数 = A 的 L 次方。默认字母表 64 个字符、长度 21,于是熵 = 21 × 6 = 126 bits,组合数 64 的 21 次方恰好等于 2 的 126 次方,约 8.5071e+37。这个 126 不是凑出来的整数,它就是 2 的 126 次方种等概率取法的对数。
字母表为什么偏偏是 64?浏览器的 crypto.getRandomValues 每次给一个 0 到 255 的字节,取这个字节的 & 63(等价于对 64 取模)就能无偏地落在 0 到 63,因为 256 % 64 恰好等于 0。若字母表只有 62 个字符,256 % 62 = 8,直接取模会让前 8 个字符的出现概率偏高,必须额外做拒绝采样才能补齐均匀性。这才是 NanoID 选 64 而不是 62 的真正原因,也是「把容易混淆的字符去掉」这种做法要付出代价的地方。
与 UUID v4 对比能更直观地看出取舍:UUID v4 只有 122 bits 随机(有 4 bit 被版本号与变体位占用),写成 36 个字符且含 4 个连字符;NanoID 用 21 个字符给到 126 bits,多 4 bits 还短 15 个字符,在 URL、数据库索引与日志行里的体积优势都很明显。
碰撞概率按生日悖论估算:p ≈ 1 − exp(−n² / (2N)),反解得到 n = √(−2N × ln(1−p))。默认参数下 p 取 1% 时 n 约 1.3077e+18 个,按每秒生成 1000 个算要 4143.7 万年,按每秒 10 亿个算也要 41.44 年;p 降到 0.1% 时约 4.1258e+17 个(1000 个/秒约 1307.4 万年),降到百万分之一时约 1.3044e+16 个(1000 个/秒约 41.33 万年)。
本页的确定性序列用线性同余发生器:s = (s × 1103515245 + 12345) & 0x7fffffff,每取一个字符推进一次,取字符用 A[Math.floor(s / 2147483647 × 字母表长度) % 字母表长度];种子先按字符码累加再 & 0x7fffffff,所以字符串 nano 折成 428。它只用于让结果可复现,不具备密码学强度。
| 变量 | 含义 | 取值 | 影响 |
|---|---|---|---|
| seed | 随机种子 | 任意字符串,字符码累加后折成整数 | 决定整条序列,同种子结果逐字相同 |
| len | ID 长度 | 1 ~ 256 的整数 | 熵按 len × log₂A 线性增长 |
| cnt | 生成数量 | 1 ~ 1000 的整数 | 只决定输出行数,不影响单个 ID 的熵 |
| alpha | 字母表 | ≥ 2 个互不相同的字符 | 有重复字符会拉低有效熵,页面直接报错 |
| ent | 熵 | len × log₂A,单位 bits | 摘要与对照表都按它推导组合数与碰撞数 |
完整示例
默认示例的种子是 nano、长度 21、数量 5。种子 nano 按字符码累加折成 428,推进 21 次得到第一个 ID,再继续推进依次得到后面四个——五个 ID 共用同一条序列,不是各自独立重开五次。结果区上方那一行摘要就是本次的字母表 64 字符、熵 126.0000 bits、组合数 8.5071e+37、碰撞 1% 需约 1.3077e+18 个。
种子 nano(折 428)· 长度 21 · 数量 5 · 字母表 64 字符 1 7d8k-7dqyyIP5XH962GyI 2 RJ1OEPNwY-KRlrcmguGCL 3 QxcPCjbhhjB4GNz8ha80U 4 wQYdqdM7u7zUSIIha-GOJ 5 VEbcrHznow6iETTnLWVu5 摘要 熵 126.0000 bits · 组合数 8.5071e+37 · 碰撞 1% 需约 1.3077e+18 个
把种子改成 20261025(折 402),其余参数不动,会得到 k72OI58bjNrlLiSls2OhA、1LRqxUMOcL50lWx3lQZ2D、DLDpIubBJJXSNohq2wW5q、yzGSSqbPbzdyZQk9J9MCy、VUtQi7EKR9ufdfNiSY6_A——注意其中出现了下划线,说明字母表末尾的两个符号同样参与取字符。把「自定义字母表」改成 0123456789abcdef 这 16 个字符,长度与数量不动,摘要里的熵会从 126.0000 bits 掉到 84.0000 bits,碰撞所需个数随之大幅缩小,页面会当场给出新的数字,不需要你重新推导。
长度与字母表对照表
下表共 14 行:前 8 行固定字母表为 64 个字符、只改长度;后 6 行固定长度为 21、只改字母表大小。两档的熵都由同一条公式 L × log₂A 得出,可以横向比较;默认显示 12 行,点工具区的「加载更多」看余下 2 行。
| 档位 | 取值 | 熵(bits) | 说明 |
|---|---|---|---|
| 长度 | 8 | 48.0000 | 组合数 2^48 |
| 长度 | 10 | 60.0000 | 组合数 2^60 |
| 长度 | 12 | 72.0000 | 组合数 2^72 |
| 长度 | 16 | 96.0000 | 组合数 2^96 |
| 长度 | 21 | 126.0000 | 组合数 2^126 |
| 长度 | 24 | 144.0000 | 组合数 2^144 |
| 长度 | 32 | 192.0000 | 组合数 2^192 |
| 长度 | 36 | 216.0000 | 组合数 2^216 |
| 字母表 | 16 | 84.0000 | 长度固定 21 |
| 字母表 | 32 | 105.0000 | 长度固定 21 |
| 字母表 | 36 | 108.5684 | 长度固定 21 |
| 字母表 | 62 | 125.0381 | 长度固定 21 |
| 字母表 | 64 | 126.0000 | 长度固定 21 |
| 字母表 | 94 | 137.6464 | 长度固定 21 |
横向读这张表能得出一条实用结论:想提高熵,加长度比扩字母表划算得多。字母表从 64 扩到 94(多 30 个字符)只多了 11.6464 bits,而长度从 8 加到 12(只多 4 个字符)就多了 24 bits。这也是默认参数把长度定在 21、字母表停在 64 的原因。
结果边界与错误提示
- 「自定义字母表」被清空时会自动填回默认的 64 字符表;只有 1 个字符时提示「自定义字母表只有 1 个字符:至少需要 2 个互不相同的字符,否则每一位都只有一种取法,熵为 0」。
- 字母表里出现重复字符时,提示点名具体是哪个字符重复:「自定义字母表里有重复字符「a」:重复会降低有效熵,请删去重复项后再生成」,不会静默按去重后的长度计算。
- 「ID 长度」不是 1 到 256 的整数时,提示写明当前填了什么、允许范围是 1 到 256;填小数或填文字同样按这条报错,不做四舍五入。
- 「生成数量」不是 1 到 1000 的整数时,提示写明允许范围是 1 到 1000;数量很大时输出区会变得很长,这是预期行为。
- 种子被清空时自动填回 nano;种子里可以包含中文、空格与符号,字符码累加后一律折成 31 位整数。
- 长度与字母表都取上限时,组合数会超出双精度浮点能表示的范围,摘要里会显示「超出双精度可表示范围」而不是给出一个假数字,碰撞个数也随之不再给出。
- 熵一律按 toFixed(4) 显示、组合数按四位有效数字的科学计数法显示,不会为凑位数补出浮点残差。
- 本页不上传任何输入,也不写本地存储;导出的对照表 CSV 带字节序标记,复制结果为纯文本。
常见问题
为什么默认长度是 21?
因为默认字母表是 64 个字符,21 × log₂64 正好等于 126 bits,比 UUID v4 的 122 bits 随机位还多 4 bits,而字符数只有 21 个。
为什么不去掉容易混淆的 0、O、l、1?
去掉之后字母表只剩 62 个字符,256 % 62 = 8,随机字节直接取模会让前 8 个字符偏多,必须额外做拒绝采样才能保持均匀。64 这个数是配合随机字节的无偏取模选的,从 64 减到 62 付出的实现代价远大于少两个易混字符的收益。
换一个种子结果会完全不同吗?
会。种子先按字符码累加再折成 31 位整数,之后整条序列都由它推进;种子差一个字符,第一个字符的取值就会改变,后面随之全变。
126 bits 到底有多安全?
按生日悖论,达到 1% 碰撞概率需要生成约 1.3077e+18 个 ID;每秒生成 1000 个要 4143.7 万年,就算每秒生成 10 亿个也要 41.44 年。对绝大多数业务来说,这个量级意味着「在可预见的时间里不需要考虑碰撞」,剩下的风险只来自随机源本身。
能直接拿这里的 ID 当会话 ID 吗?
不能。本页的同种子可复现是靠线性同余这种确定性算法实现的,猜到种子就能把整条序列复算出来,不具备不可预测性。会话 ID、密码重置令牌这类场景必须改用官方 NanoID 库或 crypto.getRandomValues。
字母表里有重复字符会怎样?
直接报错并点名重复的字符。重复字符会让某些取法的概率翻倍,实际熵低于按字符数算出来的值,页面拒绝按去重后的长度计算,是为了不让你拿到一个「看起来熵够、其实不够」的参数组合。
长度填到 256 会怎样?
ID 会正常生成,但组合数 64 的 256 次方远超双精度浮点上限,摘要里会显示「超出双精度可表示范围」,碰撞个数也不再给出。这么长的 ID 在实际业务里几乎用不上,通常是为了验证极端输入才会填。
导出的 CSV 带字节序标记吗?
带。对照表 CSV 导出时写入 UTF-8 字节序标记,Excel 双击不乱码;「复制对照表」是制表符分隔文本,粘进表格软件会自动分列。