使用方法
- 本页的 GUID生成器默认停在 v1 时序:前八位来自当前时刻,中间两组写版本号与 clock_seq,最后十二位是随机节点标识。想拿不可预测的值就切到 v4,v4 的 122 位全部随机,看不出生成时间。
- 「生成数量」默认 10,上限 200。填 201 或 0 会被直接拒绝并说明允许范围,不会悄悄改成 200。
- 「输出形式」默认是 D(36 字符)。切到 N 是 32 字符,B 与 P 各 38 字符,X 是 68 字符的十六进制数组写法,U 是带
urn:uuid:前缀的 45 字符。 - 「字母大小写」默认小写;SQL Server 的
NEWID()与 Windows 注册表里的 CLSID 习惯用大写,需要对照这类系统时切一下即可,长度不变。 - 把已有的 GUID 粘进「反查」框,点「反查版本与时刻」:逐行报出版本号、变体位是否合法;若是 v1,还会把时间戳还原成 UTC 时刻(示例第一行还原为 2026-10-03T00:00:00.000Z)。
- 「复制结果」一次带走全部行;「导出 CSV」下载 UTF-8 带 BOM 的文件,Excel 打开不乱码;下面的对照表同样能导出 CSV。
这个 GUID生成器能解决什么问题
一是把「时间版」和「随机版」的取舍摊到台面上。很多系统要的是「大体按生成顺序递增」的键——数据库按 GUID 建聚集索引时,纯随机的 v4 会让插入点散落在整棵索引里,页分裂变多;v1 的最高位组虽然不是严格递增(下面会说为什么),但至少把时间信息带在串里,便于按段排查。这个 GUID生成器把两个版本放在同一个下拉里,切换成本为零。
二是解决「同一个值在不同系统里长得不一样」。同一个 128 位的数,在 .NET 里 ToString() 出来是 36 字符,在 Windows 注册表里带着花括号是 38 字符,进 Oracle 的 SYS_GUID() 又变成 32 字符大写且字节序不同。跨系统对账时先在本页把形态统一,比在数据库里写替换函数省事。
三是把「这串到底是不是 GUID、是哪个版本」这件事一次性问清楚。反查框逐行判断:去掉连字符与括号后必须是 32 个十六进制字符,从 1 数起的第 13 个字符是版本号,第 17 个字符必须落在 8 9 a b 之内,否则就是变体位写错——这类值是复制粘贴时被截断或改写的高发区。
本页不提供:基于命名空间与哈希的 v3 / v5(要先把名字按 MD5 或 SHA-1 压成摘要,属于另一套输入);也不做批量去重后的排序整理。要的是「严格递增、一眼能看出先后」的编号,请改用按递增设计的「序列号数字生成器」——时序 GUID 的前八位每 7.1583 分钟就回绕一次,做不到这件事。
生成原理:60 位 tick、模运算拆分与位写入
本页的 GUID生成器与多数语言内置实现的第一处差别在时间起点:起点不是 1970,而是 1582 年 10 月 15 日。RFC 4122 第 4.2.2 节规定 v1 的时间戳是「格里历起点以来、以 100 纳秒为单位的计数」。这个常量是 122192928000000000,十六进制写作 0x01B21DD213814000。换算成毫秒是 12219292800000,本页正是用 Date.now() + 12219292800000 得到偏移后的毫秒数,再乘 10000 得到 tick。
第二件事:1.22e17 这个量级已经超出 JS 的安全整数。Number.MAX_SAFE_INTEGER 是 9007199254740991,而 tick 是它的十几倍;直接用 ms * 10000 去算,双精度浮点在这个量级的相邻间隔是 16 个 tick(1.6 微秒),低四位会被抹平。所以本页不直接乘,而是按模运算拆三段:lo = (ms % 4294967296) * 10000 % 4294967296 取时间低 32 位,mid = Math.floor(ms / 429.4967296 * 1000) % 65536 取中间 16 位,hi = Math.floor(ms / 28147497671.0656) % 4096 取高 12 位。括号里的乘数都保持在安全整数范围内,结果与用 BigInt 精确计算完全一致。
第三件事:6 个固定位要写死。128 位的排布是 time_low 32 位 + time_mid 16 位 + time_hi_and_version 16 位 + clock_seq_hi_and_reserved 8 位 + clock_seq_low 8 位 + node 48 位。高 12 位时间之外还要塞 4 位版本号 0001,所以第三组的第一个字符在 v1 下必定是 1;clock_seq 那 8 位的高 2 位要写成变体位 10,于是第四组的第一个字符只会是 8 9 a b 四个之一。把版本号和变体位加起来是 6 个固定位,v1 因此只剩 62 位随机(14 位 clock_seq 减 2 位、加 48 位 node),v4 则剩 122 位。
第四件事:node 字段取随机,不读网卡地址。早期实现把 48 位 node 写成 MAC 地址,等于每台机器对外广播一个可追踪的标识;RFC 9562 第 5.1 节明确建议改用随机值。本页的 clock_seq 与 node 全部来自 crypto.getRandomValues,拿不到这个接口时直接报错而不退化成 Math.random。
第五件事:v4 的做法更简单也更彻底。取 16 个随机字节后改两个字节:b[6] = (b[6] & 0x0F) | 0x40 写入版本 4,b[8] = (b[8] & 0x3F) | 0x80 写入变体位,其余 122 位原样保留。这就是「随机版」的全部内容——没有任何时间成分。
完整示例(数值仅作演示)
示例条件就是页面打开时的默认值:版本 v1、输出形式 D、数量 10、小写。逐项核对:
- 每条 36 字符,10 条共 369 字符(360 个字符加 9 个换行);换到 X 形式,单条变成 68 字符,200 条一次性输出就是 13799 字符;
- 示例前八位是
605d0000,对应 2026-10-03T00:00:00Z 这一刻 tick 值的低 32 位;第三组以1开头(11f1),第四组以b/9/a开头,都在变体合法集合内; - 10 条里只有最后 18 位不同:前八位与第三组完全相同,说明它们出自同一时刻,靠 clock_seq 与 node 区分;
- 把「生成数量」改成 201 再点生成,会被告知「请填 1~200 之间的整数,当前是「201」」,结果区清空——不做静默截断;
- 把示例第一行粘进反查框,输出「v1,变体合法,生成时刻 ≈ 2026-10-03T00:00:00.000Z」;换成第二行(
c21e25b5-…)则报 v4,且不给出时刻,因为 v4 里根本没有时间字段。
各语言与系统的默认输出对照表
同一个 128 位的值,进了不同系统会长成不同样子。下表按「调用方式 / 版本 / 默认输出 / 字符长度 / 说明」列出 24 种常见情形,跨系统对账时先在这里把形态对齐。「字符长度」一栏是文本形式的长度,不含结尾换行。
| 调用方式 | 版本 | 默认输出 | 字符长度 | 说明 |
|---|---|---|---|---|
| .NET Guid.NewGuid() | v4 | D | 36 | 小写,ToString() 默认形式 |
| .NET ToString("N") | 任意 | N | 32 | 去掉连字符,适合做文件名 |
| .NET ToString("B") | 任意 | B | 38 | 花括号,COM 与注册表常见 |
| .NET ToString("P") | 任意 | P | 38 | 圆括号,配置文件里多见 |
| .NET ToString("X") | 任意 | X | 68 | 十六进制数组,面向调试 |
| SQL Server NEWID() | v4 | D | 36 | 返回大写,长度与 D 相同 |
| SQL Server NEWSEQUENTIALID() | 自增型 | D | 36 | 比 NEWID 更利于索引,非标准 v1 |
| Windows 注册表 CLSID | 任意 | B | 38 | 花括号加大写,注册表惯例 |
| Java UUID.randomUUID() | v4 | D | 36 | 小写,toString 即 D 形式 |
| Python uuid.uuid4() | v4 | D | 36 | 小写;uuid.uuid1() 才是 v1 |
| Python uuid.uuid1() | v1 | D | 36 | 含时间戳与节点标识 |
| MySQL UUID() | v1 | D | 36 | 小写;UUID_TO_BIN 可存 16 字节 |
| PostgreSQL gen_random_uuid() | v4 | D | 36 | 小写,13 版起内置 |
| Oracle SYS_GUID() | 私有 | N | 32 | 大写无连字符,字节序与 RFC 打印序不同 |
| Linux /proc/sys/kernel/random/uuid | v4 | D | 36 | 小写,读一次换一个值 |
| JavaScript crypto.randomUUID() | v4 | D | 36 | 小写,需要 HTTPS 等安全上下文 |
| Go google/uuid.New() | v4 | D | 36 | 小写;NewUUID() 走 v1 |
| Rust uuid::Uuid::new_v4() | v4 | D | 36 | 小写,feature v4 |
| PHP ramsey/uuid Uuid::uuid4() | v4 | D | 36 | 小写;uuid1() 走 v1 |
| Swift UUID() | v4 | D | 36 | uuidString 返回大写 |
| MongoDB ObjectId | 不是 GUID | 十六进制 | 24 | 只有 12 字节,别与 GUID 混用 |
| Base64(16 字节) | 任意 | Base64 | 22 | 去掉等号填充后的最短文本形式 |
| BINARY(16) 存储 | 任意 | 二进制 | 16 字节 | 定长 16 字节,比 36 字符省空间 |
| URN 形式 | 任意 | urn:uuid: | 45 | 36 个字符加 9 个字符的前缀 |
输入范围、边界与精度说明
- 生成数量限 1~200 条。超过就明确报错,不做静默截断。200 条 X 形式共 13799 字符,一次放进页面还能正常滚动;再往上堆会让渲染明显变慢,需要更多请分几批导出。
- v1 的时间戳能用到公元 5236 年前后。60 位 tick 的上限是 2 的 60 次方,折合 115292150460.6847 秒,按格里历平均年 31556952 秒折算是 3653.4628 年,从 1582 年起算约到 5235.5 年。到那时时间戳会回绕。
- 前八位每 429.4967 秒(7.1583 分钟)回绕一次。time_low 只有 32 位,
ffffffff之后的下一个 tick 会变成00000000,因此两条 GUID 的前八位相同不代表时刻相同,跨过回绕点的两条也完全可能对不上大小。 - 时间精度按毫秒取,不假装到 100 纳秒。浏览器能给的
Date.now()是毫秒级,本页把它乘 10000 变成 tick,所以真正变化的是毫秒那一段;同一毫秒内生成的多条靠 clock_seq 与 node 区分,同一 tick 内可用的不同 clock_seq 是 4096 个(14 位里去掉 2 位变体)。 - 反查只认 N / D / B / P 四种形式。X 形式里的
0x与逗号、URN 里的前缀不在解析范围内;不合法的行会逐行报出「第几行、去掉连字符后是多少个字符」,不做整批放弃。 - 唯一性的量级要分清。v1 只有 62 位随机,组合数约 4.6117e+18,按生日悖论算,累积到约 2.528e+9 条才有 50% 概率出现一对重复;v4 有 122 位,同样概率需要约 2.715e+18 条。想做到「不可被猜到」,看的是随机位而不是总长度。
- 大写只是显示差异。大小写切换不改变任何一位的值,只影响十六进制字母的写法;拿去做比较前最好统一,否则
A042与a042在区分大小写的数据库里会被当成两条。
常见问题
GUID 和 UUID 是同一个东西吗?
位布局上是同一件事:128 位、同样的版本位与变体位、同样的 8-4-4-4-12 打印形式。差别在叫法与 tooling——UUID 是 RFC 4122 / RFC 9562 里的名字,GUID 是微软体系沿用的叫法(System.Guid、注册表里的 CLSID)。本页之所以用「GUID」这个词,是因为重点放在微软那套 N / D / B / P / X 格式说明符和时序版 v1 上。
v1 会不会泄露生成时间?
会。v1 的前三组就是时间戳,任何人拿到这串都能反算出生成时刻,本页的反查功能就是这么做的。如果这串会公开出去(订单号、分享链接、文件名),就别用 v1,切到 v4。
为什么按字符串排序不等于按时间排序?
因为排在最前面的 time_low 是 tick 的低32 位,它每 429.4967 秒回绕一次。回绕点附近,后生成的一条字符串可能反而更小。时间高位在第三组、中间位在第二组,字典序根本读不到「先高后低」的顺序。要做时间序,用 SQL Server 的 NEWSEQUENTIALID() 这类专门设计的顺序型,或者把时间单独存一列。
可以把 GUID 当密码或会话令牌吗?
不可以。判断标准是「对方能不能猜到」,而不是「看起来够不够乱」:v1 里只有 62 位随机且时间部分完全可推算,v4 虽然有 122 位随机,但它是为了「不重复」设计的,不等于带认证语义的令牌。要发令牌请看「密码生成器」这类按口令强度设计的工具。
X 形式为什么要 68 个字符?
它把 16 个字节拆成 11 段,每段加 0x 前缀,再套两层花括号与逗号:比 D 形式多出 32 个字符,全部是前缀和标点。它是 .NET 调试时的显示形式,不适合入库或写进 URL——同样 128 位,D 形式只要 36 字符。
数据库里存 36 字符还是 16 字节?
存 BINARY(16) 更省:一行省 20 字节,索引也小一圈。代价是读写时要做一次转换(MySQL 用 UUID_TO_BIN / BIN_TO_UUID,PostgreSQL 直接用 uuid 类型)。另外要注意 Oracle 的 SYS_GUID() 字节序与标准打印序不同,跨库搬数据时先统一成十六进制文本再比。
一次生成 200 条会出现重复吗?
不会。按生日悖论,v1 的 62 位随机要累积到约 3.045e+8 条才有 1% 的概率出现一对重复,200 条撞上的概率约 4e-15,远低于硬件出错的概率。真正会导致重复的是「复制同一条再手工改几个字符」,不是生成器本身。
我填的内容会被上传或保存吗?
不会。随机位取自浏览器本地的 crypto.getRandomValues,时间戳来自本机时钟,整个拼接与格式化都在当前页面完成,没有网络请求,也不写 Cookie 或本地存储。想自行确认,打开开发者工具的 Network 面板,多点几次「生成 GUID」,可以看到没有任何新请求发出。