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

接着分析版本3也就是namespace name-based MD5版本的实现,对应于静态工厂方法UUID#nameUUIDFromBytes():

public static UUID nameUUIDFromBytes(byte[] name) { MessageDigest md; try { md = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException nsae) { throw new InternalError("MD5 not supported", nsae); } byte[] md5Bytes = md.digest(name); md5Bytes[6] &= 0x0f; /* clear version */ md5Bytes[6] |= 0x30; /* set to version 3 */ md5Bytes[8] &= 0x3f; /* clear variant */ md5Bytes[8] |= 0x80; /* set to IETF variant */ return new UUID(md5Bytes); }

它的后续基本处理和随机数版本基本一致(清空版本位的时候,重新设置为3),唯一明显不同的地方就是生成原始随机数的时候,采用的方式是:基于输入的name字节数组,通过MD5摘要算法生成一个MD5摘要字节数组作为原始安全随机数,返回的这个随机数刚好也是16字节长度的。使用方式很简单:

UUID uuid = UUID.nameUUIDFromBytes("throwable".getBytes());

namespace name-based MD5版本UUID的实现步骤如下:

通过输入的命名字节数组基于MD5算法生成一个16字节长度的随机数

对于生成的随机数,清空和重新设置version和variant对应的位

把重置完version和variant的随机数的所有位转移到mostSigBits和leastSigBits中

namespace name-based MD5版本的UUID强依赖于MD5算法,有个明显的特征是如果输入的byte[] name一致的情况下,会产生完全相同的UUID实例。

其他实现

其他实现主要包括:

// 完全定制mostSigBits和leastSigBits,可以参考UUID标准字段布局进行设置,也可以按照自行制定的标准 public UUID(long mostSigBits, long leastSigBits) { this.mostSigBits = mostSigBits; this.leastSigBits = leastSigBits; } // 基于字符串格式8-4-4-4-12的UUID输入,重新解析出mostSigBits和leastSigBits,这个静态工厂方法也不常用,里面的位运算也不进行详细探究 public static UUID fromString(String name) { int len = name.length(); if (len > 36) { throw new IllegalArgumentException("UUID string too large"); } int dash1 = name.indexOf('-', 0); int dash2 = name.indexOf('-', dash1 + 1); int dash3 = name.indexOf('-', dash2 + 1); int dash4 = name.indexOf('-', dash3 + 1); int dash5 = name.indexOf('-', dash4 + 1); if (dash4 < 0 || dash5 >= 0) { throw new IllegalArgumentException("Invalid UUID string: " + name); } long mostSigBits = Long.parseLong(name, 0, dash1, 16) & 0xffffffffL; mostSigBits <<= 16; mostSigBits |= Long.parseLong(name, dash1 + 1, dash2, 16) & 0xffffL; mostSigBits <<= 16; mostSigBits |= Long.parseLong(name, dash2 + 1, dash3, 16) & 0xffffL; long leastSigBits = Long.parseLong(name, dash3 + 1, dash4, 16) & 0xffffL; leastSigBits <<= 48; leastSigBits |= Long.parseLong(name, dash4 + 1, len, 16) & 0xffffffffffffL; return new UUID(mostSigBits, leastSigBits); } 格式化输出

格式化输出体现在UUID#toString()方法,这个方法会把mostSigBits和leastSigBits格式化为8-4-4-4-12的形式,这里详细分析一下格式化的过程。首先从注释上看格式是:

<time_low>-<time_mid>-<time_high_and_version>-<variant_and_sequence>-<node> time_low = 4 * <hexOctet> => 4个16进制8位字符 time_mid = 2 * <hexOctet> => 2个16进制8位字符 time_high_and_version = 4 * <hexOctet> => 2个16进制8位字符 variant_and_sequence = 4 * <hexOctet> => 2个16进制8位字符 node = 4 * <hexOctet> => 6个16进制8位字符 hexOctet = <hexDigit><hexDigit>(2个hexDigit) hexDigit = 0-9a-F(其实就是16进制的字符)

和前文布局分析时候的提到的内容一致。UUID#toString()方法源码如下:

private static final JavaLangAccess jla = SharedSecrets.getJavaLangAccess(); public String toString() { return jla.fastUUID(leastSigBits, mostSigBits); } ↓↓↓↓↓↓↓↓↓↓↓↓ // java.lang.System private static void setJavaLangAccess() { SharedSecrets.setJavaLangAccess(new JavaLangAccess() { public String fastUUID(long lsb, long msb) { return Long.fastUUID(lsb, msb); } } ↓↓↓↓↓↓↓↓↓↓↓↓ // java.lang.Long static String fastUUID(long lsb, long msb) { // COMPACT_STRINGS在String类中默认为true,所以会命中if分支 if (COMPACT_STRINGS) { // 初始化36长度的字节数组 byte[] buf = new byte[36]; // lsb的低48位转换为16进制格式写入到buf中 - node => 位置[24,35] formatUnsignedLong0(lsb, 4, buf, 24, 12); // lsb的高16位转换为16进制格式写入到buf中 - variant_and_sequence => 位置[19,22] formatUnsignedLong0(lsb >>> 48, 4, buf, 19, 4); // msb的低16位转换为16进制格式写入到buf中 - time_high_and_version => 位置[14,17] formatUnsignedLong0(msb, 4, buf, 14, 4); // msb的中16位转换为16进制格式写入到buf中 - time_mid => 位置[9,12] formatUnsignedLong0(msb >>> 16, 4, buf, 9, 4); // msb的高32位转换为16进制格式写入到buf中 - time_low => 位置[0,7] formatUnsignedLong0(msb >>> 32, 4, buf, 0, 8); // 空余的字节槽位插入'-',刚好占用了4个字节 buf[23] = '-'; buf[18] = '-'; buf[13] = '-'; buf[8] = '-'; // 基于处理好的字节数组,实例化String,并且编码指定为LATIN1 return new String(buf, LATIN1); } else { byte[] buf = new byte[72]; formatUnsignedLong0UTF16(lsb, 4, buf, 24, 12); formatUnsignedLong0UTF16(lsb >>> 48, 4, buf, 19, 4); formatUnsignedLong0UTF16(msb, 4, buf, 14, 4); formatUnsignedLong0UTF16(msb >>> 16, 4, buf, 9, 4); formatUnsignedLong0UTF16(msb >>> 32, 4, buf, 0, 8); StringUTF16.putChar(buf, 23, '-'); StringUTF16.putChar(buf, 18, '-'); StringUTF16.putChar(buf, 13, '-'); StringUTF16.putChar(buf, 8, '-'); return new String(buf, UTF16); } } /** * 格式化无符号的长整型,填充到字节缓冲区buf中,如果长度len超过了输入值的ASCII格式表示,则会使用0进行填充 * 这个方法就是把输入长整型值val,对应一段长度的位,填充到字节数组buf中,len控制写入字符的长度,offset控制写入buf的起始位置 * 而shift参数决定基础格式,4是16进制,1是2进制,3是8位 */ static void formatUnsignedLong0(long val, int shift, byte[] buf, int offset, int len) { int charPos = offset + len; int radix = 1 << shift; int mask = radix - 1; do { buf[--charPos] = (byte)Integer.digits[((int) val) & mask]; val >>>= shift; } while (charPos > offset); } 比较相关的方法

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

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