冷饭新炒:理解JDK中UUID的底层实现 (3)

可以验证一下不同静态工厂方法的版本和变体号:

UUID uuid = UUID.randomUUID(); int version = uuid.version(); int variant = uuid.variant(); System.out.println(String.format("version:%d,variant:%d", version, variant)); uuid = UUID.nameUUIDFromBytes(new byte[0]); version = uuid.version(); variant = uuid.variant(); System.out.println(String.format("version:%d,variant:%d", version, variant)); // 输出结果 version:4,variant:2 version:3,variant:2 探究JDK中UUID源码实现

java.util.UUID被final修饰,实现了Serializable和Comparable接口,从一般理解上看,有下面的特定:

不可变,一般来说工具类都是这样定义的

可序列化和反序列化

不同的对象之间可以进行比较,比较方法后面会分析

下面会从不同的方面分析一下java.util.UUID的源码实现:

属性和构造函数

随机数版本实现

namespace name-based MD5版本实现

其他实现

格式化输出

比较相关的方法

属性和构造函数

前面反复提到JDK中只提供了版本3和版本4的实现,但是java.util.UUID的布局采用了UUID规范中的字段定义,长度一共128比特,刚好可以存放在两个long类型的整数中,所以看到了UUID类中存在两个long类型的整型数值:

public final class UUID implements java.io.Serializable, Comparable<UUID> { // 暂时省略其他代码 /* * The most significant 64 bits of this UUID. * UUID中有效的高64比特 * * @serial */ private final long mostSigBits; /* * The least significant 64 bits of this UUID. * UUID中有效的低64比特 * * @serial */ private final long leastSigBits; // 暂时省略其他代码 }

从UUID类注释中可以看到具体的字段布局如下:

高64比特mostSigBits的布局

字段 bit长度 16进制字符长度
time_low   32   8  
time_mid   16   4  
version   4   1  
time_hi   12   3  

低64比特leastSigBits的布局

字段 bit长度 16进制字符长度
variant   2   小于1  
clock_seq   14   variant和clock_seq加起来等于4  
node   48   12  

接着看UUID的其他成员属性和构造函数:

public final class UUID implements java.io.Serializable, Comparable<UUID> { // 暂时省略其他代码 // Java语言访问类,里面存放了很多底层相关的访问或者转换方法,在UUID中主要是toString()实例方法用来格式化成8-4-4-4-12的形式,委托到Long.fastUUID()方法 private static final JavaLangAccess jla = SharedSecrets.getJavaLangAccess(); // 静态内部类确保SecureRandom初始化,用于版本4的随机数UUID版本生成安全随机数 private static class Holder { static final SecureRandom numberGenerator = new SecureRandom(); } // 通过长度为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; } // 直接指定mostSigBits和leastSigBits构造UUID实例 public UUID(long mostSigBits, long leastSigBits) { this.mostSigBits = mostSigBits; this.leastSigBits = leastSigBits; } // 暂时省略其他代码 }

私有构造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;

输入的字节数组长度为16,mostSigBits由字节数组的前8个字节转换而来,而leastSigBits由字节数组的后8个字节转换而来。中间变量msb或者lsb在提取字节位进行计算的时候:

先进行左移8位确保需要计算的位为0,已经计算好的位移动到左边

然后右边需要提取的字节data[i]的8位会先和0xff(补码1111 1111)进行或运算,确保不足8位的高位被补充为0,超过8位的高位会被截断为低8位,也就是data[i] & 0xff确保得到的补码为8位

前面两步的结果再进行或运算

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zywysp.html