NanoID生成器
Generate tiny URL-safe unique IDs with customizable alphabet
字母表
长度
数量
点击"生成"以创建 NanoID
什么是 NanoID?
NanoID 是一个小巧、快速、URL 安全的随机 ID 生成器。默认情况下,它使用 64 字符字母表(A-Za-z0-9_-)生成 21 字符字符串,提供约 126 位熵——与 UUID v4 的 122 位相当,但字符串更短。
NanoID 不嵌入时间戳或任何结构化数据。每个 ID 都是纯随机的,由操作系统的加密安全随机数生成器生成(浏览器中的 crypto.getRandomValues(),Node.js 中的 crypto.randomBytes())。
NanoID vs UUID v4
NanoID 和 UUID v4 都是由 CSPRNG 支持的随机 ID 生成器。它们在格式、长度和生态系统支持方面有所不同:
| 属性 | NanoID(默认) | UUID v4 |
|---|---|---|
| 格式 | URL 安全字母数字 + _- | 连字符十六进制 |
| 长度 | 21 个字符(默认) | 36 个字符 |
| 熵 | 约 126 位 | 122 位 |
| URL 安全 | 是——无需编码 | 是(含连字符) |
| 字母表 | 64 个字符(A-Za-z0-9_-) | 16 个字符(0-9a-f) |
| 依赖项 | 需要 npm 包 | 原生(crypto.randomUUID) |
| 可自定义 | 是——长度和字母表 | 否 |
| 标准 | 无(社区库) | RFC 4122 / RFC 9562 |
当与外部系统的互操作性很重要时选择 UUID v4——具有原生 UUID 列的数据库、期望 UUID 格式的 API,或解析 UUID 的日志基础设施。当你想要更短的 ID 并控制整个技术栈时选择 NanoID。
按大小划分的碰撞概率
NanoID 的碰撞概率取决于 ID 长度和生成速率。下表使用默认的 64 字符字母表:
| 大小(字符) | 可能的 ID 数量 | 碰撞安全性 |
|---|---|---|
| 6 | 64 | 约 1 / 45 亿——适合几千个 ID |
| 8 | 64 | 约 1 / 4.5 万亿——适合数百万个 ID |
| 11 | 64 | 约 1 / 2.8 千万亿——适合数十亿个 ID |
| 16 | 64 | 约 1 / 1.2 × 10^19——适合数万亿个 ID |
| 21 | 64 | 约 1 / 10^30——适合每天 1000 亿个 ID,持续几百年 |
| 32 | 64 | 与 UUID v4 相当(122 位) |
| 36 | 36 | 超过 UUID v4 |
默认 21 字符大小的选择是为了在更短 41% 的同时匹配 UUID v4 的抗碰撞性(约 126 位)。对于大多数应用,21 个字符是正确的选择。
自定义字母表
NanoID 的字母表完全可自定义。该库接受任何唯一字符字符串作为字母表,并仅使用这些字符生成 ID:
A-Za-z0-9_-A-Za-z0-90-9a-f0-9重要:仅对非安全敏感的应用程序使用 nanoid/non-secure(例如 UI 元素 ID)。对于任何需要不可猜测的 ID,始终使用默认的安全导入。
NanoID 如何生成随机性
NanoID 使用操作系统的加密安全伪随机数生成器(CSPRNG)。在浏览器中是 crypto.getRandomValues();在 Node.js 中是 crypto.randomFillSync()。这是用于 TLS 会话密钥的相同熵源——远比 Math.random() 更强。
拒绝采样(避免模运算偏差)
一种生成随机字符的朴素方法是:取一个随机字节(0–255)并计算 byte % alphabetSize。当字母表大小不能整除 256 时,这会引入模运算偏差——某些字符出现的频率略高于其他字符。
NanoID 使用拒绝采样消除这种偏差:
- 确定覆盖字母表大小的最小 2 的幂掩码(例如对于 64 字符字母表,掩码为 63 = 0b00111111)
- 生成随机字节并应用掩码:
byte & mask - 如果掩码后的值在字母表范围内,则使用它。否则,丢弃并重试。
这意味着一些随机字节被丢弃,但结果是在字母表上均匀分布——没有任何字符比其他字符更可能出现。
// Pure browser — no npm package needed
function generateNanoid(alphabet, size) {
const mask = (2 << (31 - Math.clz32((alphabet.length - 1) | 1))) - 1
const step = Math.ceil((1.6 * mask * size) / alphabet.length)
let id = ''
while (id.length < size) {
const bytes = crypto.getRandomValues(new Uint8Array(step))
for (const byte of bytes) {
const idx = byte & mask
if (idx < alphabet.length) {
id += alphabet[idx]
if (id.length === size) break
}
}
}
return id
}
const URL_SAFE = 'useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict'
generateNanoid(URL_SAFE, 21) // → "V1StGXR8_Z5jdHi6B-myT"环境支持
代码示例
JavaScript / TypeScript
// npm i nanoid
import { nanoid } from 'nanoid'
nanoid() // → "V1StGXR8_Z5jdHi6B-myT" (21 chars, URL-safe)
nanoid(8) // → "Uakgb_J5" (custom size)
// Custom alphabet
import { customAlphabet } from 'nanoid'
const hexId = customAlphabet('0123456789abcdef', 16)
hexId() // → "4f3a1b8c9d2e0f7a"
const numId = customAlphabet('0123456789', 8)
numId() // → "30812894"浏览器(CDN)
NanoID 可以通过 CDN 导入在浏览器中直接使用。无需构建步骤即可快速原型开发。
// Pure browser — no npm package needed
function generateNanoid(alphabet, size) {
const mask = (2 << (31 - Math.clz32((alphabet.length - 1) | 1))) - 1
const step = Math.ceil((1.6 * mask * size) / alphabet.length)
let id = ''
while (id.length < size) {
const bytes = crypto.getRandomValues(new Uint8Array(step))
for (const byte of bytes) {
const idx = byte & mask
if (idx < alphabet.length) {
id += alphabet[idx]
if (id.length === size) break
}
}
}
return id
}
const URL_SAFE = 'useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict'
generateNanoid(URL_SAFE, 21) // → "V1StGXR8_Z5jdHi6B-myT"Python
# pip install nanoid
from nanoid import generate
generate() # → "V1StGXR8_Z5jdHi6B-myT"
generate(size=8) # → "Uakgb_J5"
generate('0123456789abcdef', 16) # custom alphabet + sizeNode.js(CommonJS)
// Node.js — stdlib only, no npm needed
const { randomFillSync } = require('crypto')
function nanoid(alphabet, size) {
const mask = (2 << (31 - Math.clz32((alphabet.length - 1) | 1))) - 1
const step = Math.ceil((1.6 * mask * size) / alphabet.length)
let id = ''
while (id.length < size) {
const bytes = randomFillSync(Buffer.alloc(step))
for (const byte of bytes) {
const idx = byte & mask
if (idx < alphabet.length) { id += alphabet[idx]; if (id.length === size) break }
}
}
return id
}