一个模拟过程如下:
(为了区分明显,笔者每4位加了一个下划线) (为了简答,只看字节数组的前4个字节,同时只看long类型的前4个字节) 0xff === 1111_1111 long msb = 0 => 0000_0000 0000_0000 0000_0000 0000_0000 byte[] data 0000_0001 0000_0010 0000_0100 0000_1000 i = 0(第一轮) msb << 8 = 0000_0000 0000_0000 0000_0000 0000_0000 data[i] & 0xff = 0000_0001 & 1111_1111 = 0000_0001 (msb << 8) | (data[i] & 0xff) = 0000_0000 0000_0000 0000_0000 0000_0001 (第一轮 msb = 0000_0000 0000_0000 0000_0000 0000_0001) i = 1(第二轮) msb << 8 = 0000_0000 0000_0000 0000_0001 0000_0000 data[i] & 0xff = 0000_0010 & 1111_1111 = 0000_0010 (msb << 8) | (data[i] & 0xff) = 0000_0000 0000_0000 0000_0001 0000_0010 (第二轮 msb = 0000_0000 0000_0000 0000_0001 0000_0010) i = 2(第三轮) msb << 8 = 0000_0000 0000_0001 0000_0010 0000_0000 data[i] & 0xff = 0000_0100 & 1111_1111 = 0000_0100 (msb << 8) | (data[i] & 0xff) = 0000_0000 0000_0001 0000_0010 0000_0100 (第三轮 msb = 0000_0000 0000_0001 0000_0010 0000_0100) i = 3(第四轮) msb << 8 = 0000_0001 0000_0010 0000_0100 0000000 data[i] & 0xff = 0000_1000 & 1111_1111 = 0000_1000 (msb << 8) | (data[i] & 0xff) = 0000_0001 0000_0010 0000_0100 0000_1000 (第四轮 msb = 0000_0001 0000_0010 0000_0100 0000_1000)以此类推,这个私有构造函数执行完毕后,长度为16的字节数组的所有位就会转移到mostSigBits和leastSigBits中。
随机数版本实现构造函数分析完,接着分析重磅的静态工厂方法UUID#randomUUID(),这是使用频率最高的一个方法:
public static UUID randomUUID() { // 静态内部类Holder持有的SecureRandom实例,确保提前初始化 SecureRandom ng = Holder.numberGenerator; // 生成一个16字节的安全随机数,放在长度为16的字节数组中 byte[] randomBytes = new byte[16]; ng.nextBytes(randomBytes); // 清空版本号所在的位,重新设置为4 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); }关于上面的位运算,这里可以使用极端的例子进行推演:
假设randomBytes[6] = 1111_1111 // 清空version位 randomBytes[6] &= 0x0f => 1111_1111 & 0000_1111 = 0000_1111 得到randomBytes[6] = 0000_1111 (这里可见高4比特被清空为0) // 设置version位为整数4 => 十六进制0x40 => 二级制补码0100_0000 randomBytes[6] |= 0x40 => 0000_1111 | 0100_0000 = 0100_1111 得到randomBytes[6] = 0100_1111 结果:version位 => 0100(4 bit)=> 对应十进制数4 同理 假设randomBytes[8] = 1111_1111 // 清空variant位 randomBytes[8] &= 0x3f => 1111_1111 & 0011_1111 = 0011_1111 // 设置variant位为整数128 => 十六进制0x80 => 二级制补码1000_0000 (这里取左边高位2位) randomBytes[8] |= 0x80 => 0011_1111 | 1000_0000 = 1011_1111 结果:variant位 => 10(2 bit)=> 对应十进制数2关于UUID里面的Getter方法例如version()、variant()其实就是找到对应的位,并且转换为十进制整数返回,如果熟练使用位运算,应该不难理解,后面不会分析这类的Getter方法。
随机数版本实现强依赖于SecureRandom生成的随机数(字节数组)。SecureRandom的引擎提供者可以从sun.security.provider.SunEntries中查看,对于不同系统版本的JDK实现会选用不同的引擎,常见的如NativePRNG。JDK11配置文件$JAVA_HOME/conf/security/java.security中的securerandom.source属性用于指定系统默认的随机源:
这里要提一个小知识点,想要得到密码学意义上的安全随机数,可以直接使用真随机数产生器产生的随机数,或者使用真随机数产生器产生的随机数做种子。通过查找一些资料得知非物理真随机数产生器有:
Linux操作系统的/dev/random设备接口
Windows操作系统的CryptGenRandom接口
如果不修改java.security配置文件,默认随机数提供引擎会根据不同的操作系统选用不同的实现,这里不进行深究。在Linux环境下,SecureRandom实例化后,不通过setSeed()方法设置随机数作为种子,默认就是使用/dev/random提供的安全随机数接口获取种子,产生的随机数是密码学意义上的安全随机数。一句话概括,UUID中的私有静态内部类Holder中的SecureRandom实例可以产生安全随机数,这个是JDK实现UUID版本4的一个重要前提。这里总结一下随机数版本UUID的实现步骤:
通过SecureRandom依赖提供的安全随机数接口获取种子,生成一个16字节的随机数(字节数组)
对于生成的随机数,清空和重新设置version和variant对应的位
把重置完version和variant的随机数的所有位转移到mostSigBits和leastSigBits中
namespace name-based MD5版本实现