NanoID生成器_短ID批量生成与熵值计算

按 NanoID 默认的 64 字符字母表生成定长短 ID,默认长度 21 对应 126 bits 熵;页面同时把组合数与生日碰撞概率算出来——长度 21 时达到 1% 碰撞概率需要生成约 1.3077e+18 个,按每秒 1000 个算要 4143.7 万年。为让结果可复现,本页改用确定性序列,不用于生产密钥。

种子 nano 按字符码累加折成 428 · 确定性线性同余序列,同一种子同一参数结果逐字可复现 · 不可用于生产环境的密钥与会话 ID。

四个输入框任意一个按回车都会立即重新生成;字母表被清空时会自动填回默认的 64 字符表,种子被清空时会自动填回 nano。对照表默认显示 12 行,点「加载更多」看余下 2 行。

使用方法

  1. 打开页面就有一组完整结果:种子是 nano、长度 21、数量 5、字母表为 NanoID 默认的 64 个字符,结果区已经列出 5 个 ID,不需要先填任何东西。
  2. 想换一批就改「随机种子」:任意文字都行,同一个种子配上同一套长度与字母表,永远得到同一串 ID,方便把结果贴进文档、测试用例或工单之后原样复现。
  3. 「ID 长度」默认 21,可填 1 到 256;「生成数量」默认 5,可填 1 到 1000。三个输入框任意一个按回车都会立即重新生成。
  4. 「自定义字母表」默认填的就是 A 到 Z、a 到 z、0 到 9 外加短横与下划线的 64 个字符。改成十六进制的 16 个字符会明显降熵,摘要里的熵与碰撞数字会同步变小。
  5. 点「立即生成」或改动任一输入框都会重算;结果区那一行是本次的字母表大小、熵、组合数与碰撞 1% 所需个数,是当场算出来的,不是固定文案。
  6. 结果区下面是 ID 列表,一行一个;「复制结果」复制这份纯文本,可直接粘进代码或表格。
  7. 下方「长度与字母表对照表」共 14 行,默认显示 12 行,点「加载更多」看余下 2 行;「导出 CSV」保存为带字节序标记的文件,「复制对照表」复制制表符分隔文本。
  8. 本页不联网、不写本地存储,刷新即回到默认示例。

这个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随机种子任意字符串,字符码累加后折成整数决定整条序列,同种子结果逐字相同
lenID 长度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)说明
长度848.0000组合数 2^48
长度1060.0000组合数 2^60
长度1272.0000组合数 2^72
长度1696.0000组合数 2^96
长度21126.0000组合数 2^126
长度24144.0000组合数 2^144
长度32192.0000组合数 2^192
长度36216.0000组合数 2^216
字母表1684.0000长度固定 21
字母表32105.0000长度固定 21
字母表36108.5684长度固定 21
字母表62125.0381长度固定 21
字母表64126.0000长度固定 21
字母表94137.6464长度固定 21

横向读这张表能得出一条实用结论:想提高熵,加长度比扩字母表划算得多。字母表从 64 扩到 94(多 30 个字符)只多了 11.6464 bits,而长度从 8 加到 12(只多 4 个字符)就多了 24 bits。这也是默认参数把长度定在 21、字母表停在 64 的原因。

结果边界与错误提示

常见问题

为什么默认长度是 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 双击不乱码;「复制对照表」是制表符分隔文本,粘进表格软件会自动分列。

相关工具