重新聚焦UUID构建:深入分析JDK中构建UUID的逻辑
[id_[id_129[id_2069735838]926193]46413414]id_1845450026]

前言
正如在《ULID:构建分布式ID的另一种选择》中所讲,UUID通常由32个字符组成,这些字符以16进制形式呈现。其一般表现为8 - 4 - 4 - 4 - 12的形式。再加上连接字符“-”,总共是36个字符。
(图中各段的含义可参考往期内容,在此我们便不再赘述~)
JDK中与UUID相关API
JDK为方便使用UUID作为资源的唯一标识信息。它提供了java.util.UUID类。用来帮助我们快速构建UUID。具体如下:
public void testUUID() {
log.info("generating UUID : [{}]", UUID.randomUUID());
}
运行上述代码就能生成一串UUID。它的名字是7e281cab-6b44-4426-bab1-f6a1405201d9。不难发现,通过UUID.randomUUID()可生成一串版本号为4的UUID。正如我们之前所讲的在UUID规范里 对于UUID的版本 主要存在两种实现方式 一种是基于时间的version-1 另一种是完全随机的version-4
说到这里 你可能会有这样的疑问 既然UUID有两种版本的实现 那么JDK中实现的是version-4的随机版本吗 当然不是 JDK中大致提供了如下几种不同的版本信息
“时间戳UUID (Version 1)”:这是UUID最常见的版本。它依据时间戳和节点信息生成。通常包含时间戳。还包含时钟序列号。也包含节点标识等信息。
「DCE安全UUID (版本2)」:此版本的UUID虽具备DCE(分布式计算环境)的安全特性,然而在开发里却鲜少被使用
“随机生成UUID (Version 3)”:此版本的UUID采用基于名称的生成方法。通常把名称(像命名空间和名称字符串)以及特定算法当作输入来生成UUID。这种方式并不常用。
“基于MD5散列的UUID (Version 4)” 是生成随机UUID的常用方式。它借助伪随机数生成器生成UUID。其随机性良好。它还是UUID.randomUUID()方法默认使用的版本
「基于SHA-1散列的UUID (Version 5)」 :这个版本的UUID和版本3相似。它使用SHA-1散列算法生成。除此之外。它通常还需要输入名称和命名空间作为参数。
UUID相关生成原理
来分析JDK中构建UUID的相关逻辑
UUID.randomUUID()
public static UUID randomUUID() {
SecureRandom ng 等于 Holder.numberGenerator
byte[] randomBytes = new byte[16];
ng接着生成随机字节。ng获取下一批字节。这些字节是随机生成的。
randomBytes[6] &= 0x0f; /* clear version */
randomBytes[6] |= 0x40; /* set to version 4 */
randomBytes[8] &= 0x3f; /* clear variant */
randomBytes[8] |= 0x80; /* set to IETF variant */
return new UUID(randomBytes);
}
不难发现,randomUUID首先会构建一个SecureRandom对象。SecureRandom是Java中的一个类。它主要用于生成安全的伪随机数。看到随机数生成,可能你会很快想到Math库中的Random函数。虽然Random函数也能生成随机数,但两者存在差距。
具体而言 SecureRandom 是一个工具类 专门设计用来生成安全性强的伪随机数 主要应用于密码学 安全通信以及安全相关应用 SecureRandom 会采用多重底层随机源 并使用强加密算法 以此确保生成的随机数具备高度随机性和不可预测性而Random:普通的Random类生成随机数的质量比不上SecureRandom。它借助一个伪随机数生成算法来生成随机数。通常采用的是线性同余法。在某些情形下。其生成的结果实际上是能够被预测的
为避免构建重复的UUID,此处选用SecureRandom构建安全系数更高的随机数。获取随机数后,从SecureRandom中获取16个随机字节。接着进行一通位运算操作。
为分析清楚这些位运算操作究竟做了哪些操作。接下来进行假设。假设ng.nextBytes(randomBytes)读取到的内容是由1组成的随机串。也就是说randomBytes中的内容为0xFF。
首先,执行randomBytes[6] &= 0x0f,也就是FF & 0F。操作后,randomBytes[6]的结果为0F。随后,执行0F|40的操作。可以发现,经过这一操作,randomBytes[6]存储的内容为4f。后续会对randomBytes[8]进行操作。这些操作主要用来计算变体的相关信息。
注:可参考ULID:构建分布式ID的另一种选择的相关分析获取有关UUID变体的相关信息
总之若生成的16个随机字节全是11111111。那么经上述位运算后。得到的UUID的版本如下所示。得到的UUID的变体如下所示。
剩下的操作是进行组装。它要把SecureRandom生成的16个随机字节进行组装。组装成一个128位的UUID。
最终 返回生成的UUID 当16个随机字节全是11111111时 最终得到的UUID是ffffffff-ffff-4fff-8fff-ffffffffffff
至于其中的8你可能会感到疑惑。接下来,我们来解释下8的由来。在分析之前首先明确一点。8来自于UUID中的变体字段。UUID变体字段通常占据了UUID的13 - 16个字节。具体来说。UUID的第13个字节的高4位必须是固定的。第13个字节的低4位是变化的。它将在0 - 15之间。
生成UUID时 变体字段一般依规范设置 用来指示UUID的生成方法与结构 变体字段低4位虽能是0到15间任意值 但实际中通常设为10 表示基于随机数生成UUID 这就是示例UUID中8的由来
确定好UUID中的版本信息。确定好UUID中的变体内容。之后下一步要做的。是通过new UUID(randomBytes)返回一个UUID实例对象。
接下来 我们看看UUID的构造函数做了哪些操作 其内部相关代码如下
UUID的构造函数
利用长度是16的字节数组 计算mostSigBits的值 计算leastSigBits的值 用这两个值初始化UUID实例
private UUID(byte[] data) {
long msb = 0;
long lsb = 0;
assert data.length == 16 : 数据的长度必须为16字节;
for (int i=0; i<8; i++)
msb = (msb << 8) | (data[i] & 0xff);
for (int i=8; i<16; i++)
lsb = (lsb << 8) | (data[i] & 0xff);
this.mostSigBits = msb;
this.leastSigBits = lsb;
}
上述代码里的变量mostSigBits和leastSigBits,分别代表UUID的最高有效位与最低有效位。进一步讲,在UUID够赞函数内部,会借助二进制的移位操作。通过此操作,data中字节数组的所有位,会转移到mostSigBits和leastSigBits上。最终构成一个128位的UUID字符,并返回结果。
总结
在Java里,UUID.randomUUID()是一种我们常用以构建UUID信息的方法。该方法内部大致逻辑如下:
“生成随机数”:在randomUUID()内部。其会借助SecureRandom强随机数生成器产生随机数据。这个随机数是构建新UUID的基础。
「调整随机数以符合UUID规范」:按照UUID版本4的规范来。构建uuid时要提供版本号。还要提供变体信息。这两个数据位置一般是固定的。即
使用私有构造函数创建 UUID 对象:借助 SecureRandom 生成随机数。随机数经调整。调整后被分为两个 64 位数据。两个 64 位数据分别代表 UUID 的最高有效位和最低有效位。最终返回一个 UUID 实例
以上便是每次调用randomUUID()背后的逻辑。它基于强随机数生成器SecureRandom。所以生成的UUID在实践中具备非常高的唯一性与随机性。这才致使它们适宜用作数据库主键、对象标识符等。
至此 我们对UUID内部的randomUUID方法做了细致分析 希望文章能帮助你理解UUID
点击关注公众号,”技术干货”及时达!
超具体教程 为名目性能JDK JDK环境变量 Eclipse导入JDK Eclipse和JDK装置性能 (超级教程电影在线观看)
为名目性能JDK 超具体教程 JDK环境变量 Eclipse和JDK装置性能 Eclipse导入JDK
Eclipse和JDK装置性能 JDK环境变量 为名目性能JDK 超详细教程 Eclipse导入JDK (eclipse汉化包安装教程)
Eclipse和JDK装置性能 JDK环境变量 Eclipse导入JDK 超具体教程 为名目性能JDK (eclipse汉化包安装教程)
Oracle JDK各版本新特性详解:从JDK 1.0到JDK 6.0的重要更新
数据库的逻辑模型和物理模型的差异 函数的指针和指针的函数有什么区别 还有电子外面的终止是什么意思 (数据库的逻辑结构设计)