在前端开发或数据处理中,我们经常需要生成唯一标识符(ID)。标准的 Unix 时间戳(毫秒级)通常是一个 13 位的数字,比如 1703123456789
。虽然这种格式便于排序和计算,但在某些场景下——如生成短链接、构建紧凑 ID、节省存储空间或提高 URL 可读性——我们希望将其变得更短。本文将介绍几种实用的字符串 ID 生成方法,并提供简洁高效的 JavaScript 实现。
为什么需要缩短时间戳?
时间戳本身具有全局唯一性和时序性,是非常理想的 ID 基础。但它的长度(13 位数字)在以下场景中显得冗长:
- 生成短网址或邀请码
- 构建数据库中的紧凑主键
- 减少网络传输的数据量
- 提高用户界面的可读性
通过合理转换,我们可以将时间戳压缩到 6~8 个字符,同时保留其核心特性。
常见的缩短方法
使用高进制编码
JavaScript 的 Number.prototype.toString(radix)
方法支持 2~36 进制转换。进制越高,表示相同数值所需的字符越少:
const ts = Date.now();
console.log(ts.toString(16)); // 16进制,约11位
console.log(ts.toString(32)); // 32进制,约9位
console.log(ts.toString(36)); // 36进制,约8位(推荐)
36 进制使用 0-9
和 a-z
共 36 个字符,是原生支持的最高进制,兼顾简洁性和可读性。
减去基准时间戳
时间戳是从 1970 年开始计算的,但如果我们只关心近几年的数据,可以减去一个“基准时间”,只保留相对偏移量:
// 以2020年为基准
const base = new Date(2020, 0, 1).getTime();
const short = (Date.now() - base).toString(36);
这样能显著减少数值大小,从而进一步缩短编码长度。
组合策略:以当前年份为基准 + 36 进制
最实用的方案是以当前年份的开始时间为基准,再转为 36 进制。这既能大幅缩短长度,又保持了年内唯一性和时序性。
最简实现只需一行代码:
const short = (Date.now() - new Date(new Date().getFullYear(), 0)).toString(36);
这段代码:
new Date().getFullYear()
获取当前年份new Date(年份, 0)
创建该年 1 月 1 日 00:00:00 的 Date 对象- 相减得到当年内的毫秒偏移量
.toString(36)
转为 36 进制字符串
结果通常为 6~7 个字符,例如:1k9j2m
。
重要提示:这种“减去当前年份”的方式仅在父级上下文已包含年份信息时才合适。例如:
- 文件路径为
/2024/xxx
,其中xxx
使用年内偏移量- 数据库存储时有单独的
year
字段- URL 结构为
/api/2024/events/{shortId}
如果脱离年份上下文单独使用该 ID,则无法确定其真实时间,也无法保证跨年唯一性。
其他字符串 ID 生成方式
除了基于时间戳的方案,还有多种生成唯一字符串 ID 的方法:
纯随机字符串
生成指定长度的随机字符组合:
const randomId = Array.from({length: 8}, () =>
'abcdefghijklmnopqrstuvwxyz0123456789'[Math.floor(Math.random() * 36)]
).join('');
优点是简单直接,缺点是无序且存在重复风险。
UUID
使用标准 UUID v4 格式(128 位):
constd uuid = crypto.randomUUID();
// 简易 UUID 实现
const uuid2 = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
const r = Math.random() * 16 | 0;
return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
});
// 去掉连字符可得 32 位十六进制字符串
UUID 全球唯一性极强,但长度较长(32 字符无连字符)。
雪花算法(Snowflake)
分布式 ID 生成算法,结合时间戳、机器 ID 和序列号,生成 64 位整数,通常转为字符串使用。长度固定(19 位数字),有序且全局唯一,但需服务端支持。
时间戳 + 随机后缀
结合时序性和随机性:
const id = Date.now().toString(36) + Math.random().toString(36).slice(2, 5);
既有时间序,又降低同一毫秒内重复的概率。
总结:字符串 ID 生成方案对比
方案 | 示例长度 | 字符集 | 有序性 | 重复概率 | 适用场景 |
---|---|---|---|---|---|
原始时间戳 | 13 位 | 0-9 | ✅ 完全有序 | 同一毫秒内重复 | 日志、排序 |
36 进制时间戳 | 8-9 位 | 0-9, a-z | ✅ 完全有序 | 同一毫秒内重复 | 紧凑 ID、URL |
年内偏移 + 36 进制 | 6-7 位 | 0-9, a-z | ✅ 年内有序 | 同一毫秒内重复 | 需年份上下文,如文件名、子路径 |
8 位纯随机 | 8 位 | 自定义 | ❌ 无序 | 中等(≈1/2820亿) | 临时令牌、短链接 |
UUID(无连字符) | 32 位 | 0-9, a-f | ❌ 无序 | 极低(≈1/2^122) | 全局唯一 ID、数据库主键 |
Snowflake ID | 19 位 | 0-9 | ✅ 完全有序 | 几乎为零 | 分布式系统、高并发场景 |
时间戳+随机后缀 | 10-12 位 | 0-9, a-z | ✅ 基本有序 | 极低 | 通用唯一 ID,兼顾长度与唯一性 |
选择建议:
- 若需最短且有序,且有年份上下文 → 使用 年内偏移 + 36 进制
- 若需通用唯一 ID → 使用 时间戳 + 随机后缀
- 若需绝对全局唯一 → 使用 UUID
- 若在分布式系统中 → 考虑 Snowflake
记住:没有“最好”的方案,只有“最合适”当前场景的方案。根据你的业务需求、存储限制和唯一性要求,选择最平衡的实现方式。