likes
comments
collection
share

以randomUUID为例,揭秘JDK中构建UUID的原理

作者站长头像
站长
· 阅读数 28

思考,输出,沉淀。用通俗的语言陈述技术,让自己和他人都有所收获。 作者:毅航😜


前言

以randomUUID为例,揭秘JDK中构建UUID的原理

(图中各段的含义可参考往期内容,在此我们便不再赘述~)

JDK中与UUID相关API

JDK中为了方便方便快捷的使用UUID作为资源的唯一标识信息,其提供java.util.UUID类来帮助我们快速构建UUID。具体如下:


public void testUUID() {
    log.info("generating UUID : [{}]", UUID.randomUUID());
}

运行上述代码我们即可生成一串名为:7e281cab-6b44-4426-bab1-f6a1405201d9UUID。不难发现,通过UUID.randomUUID()我们即可生成一串版本号为4UUID。正如我们之前的提到的在UUID规范中,对于UUID的版本主要有两种实现方式,一种是基于时间的version-1,另一种则是完全随机的version-4

说到此,你可能会有这样的疑惑,既然对于UUID而言其有两种版本的实现,那JDK中是对version-4的随机版本进行了实现吗?当然不是,在JDK中其大致提供了如下几种不同的版本信息:

  1. 时间戳UUID (Version 1) :这是最常见的UUID版本。它基于时间戳和节点信息生成,通常包含了时间戳、时钟序列号、节点标识等信息。
  2. DCE安全UUID (Version 2) :这个版本的UUID虽然包含了DCE(Distributed Computing Environment)的安全特性,但在开发中却很少用。
  3. 随机生成UUID (Version 3) :这个版本的UUID使用基于名称的UUID生成方法,通常将名称(如命名空间和名称字符串)和一个特定的算法作为输入生成UUID。这种方式不常用。
  4. 基于MD5散列的UUID (Version 4) :这是生成随机UUID的一种常用方式。它基于伪随机数生成器生成UUID,具有良好的随机性。也是UUID.randomUUID()方法默认使用的版本。
  5. 基于SHA-1散列的UUID (Version 5) :这个版本的UUID版本3类似,只不过其使用SHA-1散列算法生成,除此之外还通常也需要输入名称和命名空间作为参数。

UUID相关生成原理

接下来,我们以java.util.UUID.randomUUID() 为例来分析JDK中构建UUID的相关逻辑。

UUID.randomUUID()

public static UUID randomUUID() {
    SecureRandom ng = Holder.numberGenerator;

    byte[] randomBytes = new byte[16];
    ng.nextBytes(randomBytes);
    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对象。这里的这个SecureRandomJava中的一个类,其主要用于生成安全的伪随机数。看到随机数生成,可能你会很快想到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]的操作则主要用于计算变体的相关信息。

总之,如果生成的16个随机字节全部都是 11111111,那么经过上述所示的位运算后,得到的UUID的版本和变体如下所示:

  • 版本: 0100
  • 变体: 10

剩下的操作就是将,它将SecureRandom生成的这16个随机字节组装成一个128位的UUID。即

  • 高64位:取前8个字节,即 FF FF FF FF FF FF FF FF
  • 低64位:取后8个字节,即 FF FF FF FF FF FF FF FF

最终,返回生成的UUID。因此当16个随机字节全部都是 11111111时,最终得到的UUID为: ffffffff-ffff-4fff-8fff-ffffffffffff

至于其中的8你可能会感到疑惑。接下来,我们来解释下8的由来。在分析之前首先明确一点,那就是8来自于UUID中的变体字段。在UUID变体字段通常占据了UUID13-16个字节。具体来说,UUID的第13个字节的高4位必须是固定的,而第13个字节的低4位是变化的,因此它将在0-15之间

但在生成UUID时,变体字段通常根据规范设置,以指示UUID的生成方法和结构。虽然变体字段的低4位可以是0到15之间的任何值,但在实际中,通常设置为10,表示UUID的生成方法是基于随机数的UUID。这也是为什么在示例UUID8的由来。

确定好了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 : "data must be 16 bytes in length";
    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;
}

上述代码中的变量mostSigBitsleastSigBits分别表示UUID的最高有效位和最低有效位。进一步,UUID够赞函数内部会通过二进制的移位操作来将data中字节数组的所有位就会转移到mostSigBitsleastSigBits之上,最后构成一个128UUID字符并将结果进行返回。

总结

Java中,UUID.randomUUID() 是一个我们常来构建UUID信息的方法。该方法内部的大致逻辑如下:

  1. 生成随机数:在 randomUUID() 内部,其会借助SecureRandom强随机数生成器来产生随机数据,而这个随机数是用于构建新 UUID 的基础。

  2. 调整随机数以符合 UUID 规范:根据UUID版本 4 的规范,其在构建uuid时需要提供版本号以及变体信息,这两个数据位置通常是固定的。即

    • 在第7位的高四位(固定为 0x4 以指示这是一个版本 4 的 UUID。
    • 在第 9 位的高两位,调整为 0x80x90xA0xB,以符合 RFC 4122 中对变体字段的要求。
  3. 使用私有构造函数创建 UUID 对象:借助SecureRandom生成的随机数经调整后被分为两个 64 位数据,其分别代表UUID的最高有效位和最低有效位。并最终返回一个 UUID 实例。

如上就是 randomUUID() 每次调用后背后的逻辑,由于其基于强随机数生成器SecureRandom,因此生成的 UUID 在实践中具有非常高的唯一性和随机性,z这才使得它们适合用作数据库主键、对象标识符等。

至此,我们就对UUID内部的randomUUID方法进行了细致的分析。希望文章对你理解UUID有所帮助!

转载自:https://juejin.cn/post/7313805883590524954
评论
请登录