口径全部照 RFC 9562(2024 年 5 月发布、把 UUID 写成现行标准的规范)落地实现,字段位宽、命名空间常量、测试向量都逐条对过。规范自己说过一句实话:UUID 最好当成不透明的值来用,别去解析里面的字段(§6.12);这个页面提供解析是为了核对与调试,不是为了让你依赖里面的字段。
随手要一个 ID、请求编号、临时文件名——v4,122 位全随机,值里不含时间也不含机器信息,猜不到。要当数据库主键并且在意索引局部性——v7,前 48 位是 Unix 毫秒时间戳,新值天然排在旧值后面,规范的措辞也是「能用 v7 就别用 v1 和 v6」(§5.7);它字符串排序就是时间排序,规范 §6.11 正是这么设计的。要把外部已有的 ID 稳定映射成 UUID(同一个用户邮箱每次都得同一个键)——v5(SHA-1);v3 走的是 MD5,规范 §5.3 明写「Where possible, UUIDv5 SHOULD be used in lieu of UUIDv3」,所以除非要兼容老数据,选 v5。v1 和 v6 留给对接老系统:v1 是 1582 年起的 100 纳秒时间戳加机器码,v6 是同样内容换了字段顺序,只因为 v1 的字符串排序不等于时间排序。Nil 和 Max 不是标识,是规范留给「这里没有值」和「列表到此为止」的占位符。
只走 crypto.getRandomValues,也就是浏览器给的密码学安全伪随机源,规范 §6.9 的要求正是「SHOULD 用 CSPRNG」。这个页面没有 Math.random 兜底:拿不到它就直接在红色提示条里停下,不会给你一串看着像随机、其实同一台机器上能被预测的值。v4 是 128 位里去掉版本 4 位和 variant 2 位,剩下 122 位全来自这个源;v7 是 74 位。
算法没有秘密:把命名空间那 16 个字节和你的名字拼在一起喂给 MD5(v3)或 SHA-1(v5),取结果最左边 128 位铺满 UUID 的 16 个字节,再把版本号 3 或 5、variant 那两位盖回对应位置。SHA-1 出 160 位,规范让丢掉最右 32 位,页面就是这么丢的。名字是按 UTF-8 的八位字节进哈希的,「名字是什么写法」决定了结果——同一个词的大小写、多余的空格、域名要不要带点结尾,都会换出一个完全不同的值;输入框下面那行「N 字节」就是喂进哈希的字节数,中文一个字 3 字节、emoji 一个 4 字节。名字留空也是合法输入,那是「命名空间自己的哈希值」,规范没禁止。但反方向不成立:从 UUID 推不回名字,所以别把密码、身份证号这类东西当名字喂进去——想枚举的人只要拿一份常见值试一遍就能对上。规范还有一条设计提醒(§6.13):别把名字 UUID 当表的主键,那些「看着永远不变」的字段后来变了,整张表的结构就跟着烂。
名字加 namespace 才是完整输入,所以「DNS + www.example.com」和「URL + www.example.com」是两个不同 UUID。官方注册的只有四个,页面下拉里就是它们,照规范 §6.6 表 3 原样填的常量:DNS 6ba7b810-…、URL 6ba7b811-…、OID 6ba7b812-…、X.500 6ba7b814-…。注意 6ba7b813-… 这一格规范明确写了没定义、不该用,页面也就没列。自家应用要用独立的命名空间,就选「自定义」粘一个进来;规范建议这种自用值本身用 v4 或 v7 生成(§6.6),别去顺着官方那四个往上加数字——那是留给 IANA 注册用的规则。
v1 和 v6 用的是同一个 60 位时间戳:从 1582 年 10 月 15 日 0 点(格里高利历改历那天)开始数的 100 纳秒格数,规范说这个宽度够用到大公元 5623 年;v7 换成 Unix 毫秒,48 位、到大公元 10889 年。「拆一个已有的 UUID」会把里面那段时间还原成 UTC 与本机时区两个写法,并告诉你它离现在多远。但读出来的时间只代表当时那台机器报的钟:规范 §6.1 允许实现自行调整时间戳,页外的值也一样,别拿它当可信时间证据。
不会。浏览器不给网页读 MAC,规范 §6.10 对这种场合写的是「MUST 用随机值当 node」,并且要把第一个字节的最低位(组播位)置 1——真网卡的地址这一位永远是 0,所以两边不会撞。这个页面在你第一次生成 v1 或 v6 时随机造一个 48 位 node、连同 14 位 clock_seq 一起在整个页面生命周期里复用(§5.1 要求 clock_seq 一辈子只随机初始化一次,且不许和 node 相关),之后每条都换新的时间戳;系统钟要是被往回拨,页面会按 §5.1 重新摇一个 clock_seq 并让时间戳继续严格往前走,所以同一批里不会出现两条一样。「拆一个已有的 UUID」里会直接告诉你这个 node 的组播位是 1(随机来的)还是 0(可能是真网卡),拆别人给的 v1 时这一点最有用。
v1、v6 的时间戳严格递增,v7 在同一毫秒里把 rand_a 那 12 位当计数器递增(一毫秒用满 4096 条就把毫秒位推前一格),所以这三种在本页面这一批里条条不同。v4 没有这种机制,靠的是概率:一次 200 条撞出同一条的概率按生日界算是 200×199÷2÷2122 ≈ 3.7×10-33;要撞有大约一半的机会,得凑到 1.1774×261 ≈ 2.7×1018 条。v7 只在一毫秒之内比那 74 位随机位,两条撞在一起的机会有 1.89×1022 分之一,同样够不着。这些是纯算术,不是对你机器上真实分布的测量。
规范正文只正式定义了 hex-and-dash 那一种(§4 的 ABNF 摊开就是 8-4-4-4-12:五段十六进制字符、中间四个横杠),并且写了字母大小写都能用;urn:uuid: 是 URN 命名空间里的写法,{ … } 是 Python 和 Microsoft 那类实现输出时的习惯,剩下 32 位无横杠、Base64、Base64url、十进制大整数都是同一份 128 位换了层皮。同一批里请挑一种保持一致——规范允许混用大小写,但你的代码多半不允许。存进数据库要存 16 个字节而不是那串字符:规范 §6.13 算过账,36 个字符的文本写法要占 288 位才能装下 128 位的信息。两个坑:Base64 的字母本身区分大小写,所以选中它的时候「改大写」按钮会灰掉,硬转就换了一份数据;存二进制时字节序是大端(网络序),而 Windows 的 COM GUID 是小端,两边直接倒字节会错位,规范把这段留在自己的范围之外。
认四种写法:8-4-4-4-12 带横杠、32 位无横杠、外面套大括号、前面加 urn:uuid:,字母大小写都收,首尾空格会被去掉。剩下的按字节拆:variant(本规范是 10xx,另三类分别是 NCS 保留、Microsoft 保留、未来定义)、版本号(0 到 15,其中 2 是 DCE Security、规范说它的定义不在这份文档里,8 是实现自定义,9 到 15 留着没定义,遇到都会照实说)、时间段的还原、v1/v6 的 clock_seq 与 node、v7 的 rand_a 与 rand_b,再附上同一份字节在七种写法下分别长什么样。全零是 Nil、全一是 Max,规范注明这两个恰好落在本规范 variant 区间之外,页面会点出来。不合法时会说清卡在哪:长度是几位、第几个字符不是十六进制、横杠位置为什么不对。
不在。不写 localStorage、不写 Cookie、不进浏览历史,生成的值和名字只活在当前页面变量里,刷新即失。地址栏 # 后面只有设置:版本、条数、写法、大小写、命名空间那一种(自定义命名空间那一串和名字本身都不写进地址栏)——发给同事点开就是同一套配置。所以「同一个名字每次都要同一个值」这种用法,请把他机器的输出和你机器的核对一遍;页面刷新后列表找不回来,先复制再生成下一批。