Java源码分析-UUID
原文链接:Little Apple's Blog
本文分析的JDK版本为1.8.0_131。
UUID?
UUID是Universally Unique Identifier的缩写;Java UUID是java.util包下的一个用于获取通用唯一标识的类。有一个别名GUID(Globally Unique Identifier)。
UUID是16字节128位长的数字,通常用36个字符的字符串表示,如下图所示:

字母是用16进制表示的,大小写无关!
UUID用途
- 文件名随机
- 网页session Id
- 数据库表主键
- 事务id
UUID定义
public final class UUID implements java.io.Serializable, Comparable<UUID>
final修饰,不可被继承
UUID属性
//UUID的最高有效字节
private final long mostSigBits;
//UUID的最低有效字节
private final long leastSigBits;
UUID构造器
public UUID(long mostSigBits, long leastSigBits) {
this.mostSigBits = mostSigBits;
this.leastSigBits = leastSigBits;
}
public公有构造器,传入有效字节,构造UUID;
UUID还提供了一个私有构造器(字节数组构造),用于创建UUID;
private UUID(byte[] data) {
long msb = 0;
long lsb = 0;
assert data.length == 16 : "data must be 16 bytes in length";
//前8个字节,64位赋值给msb;data[i] & 0xff还是自己,有什么用?
// msb << 8 | data[i] 每次左移并取或运算 msb高位56位不变,低位8位为data[i]
for (int i=0; i<8; i++)
msb = (msb << 8) | (data[i] & 0xff);
//后8个字节,64位赋值给lsb
for (int i=8; i<16; i++)
lsb = (lsb << 8) | (data[i] & 0xff);
this.mostSigBits = msb;
this.leastSigBits = lsb;
}
方法私有,只有内部使用;确保传入data字节数组的长度是16;
UUID的版本及其变体
为了兼容和应对未来的变化,UUID会有很多的版本以及变体;下图所示:

UUID变体
- 0xxx (Reserved for NCS backward compatibility)预留,为向后兼容
- 10xx (Leach-Salz)当前正在使用的
- 110x (Reserved, Microsoft Corporation backward compatibility)微软早起GUID预留
- 111x (Reserved for future definition)将来扩展预留,目前还没被使用
UUID版本
time-based 基于时间的UUID(版本 1)
通过计算当前时间戳、随机数和机器MAC地址得到。由于有MAC地址,这个可以保证其在全球的唯一性。但是使用了MAC地址,就会有MAC地址暴露问题。若是局域网,可以用IP地址代替。
DCE Security DCE安全的UUID(版本 2)
DCE(Distributed Computing Environment)安全的UUID和基于时间的UUID算法相同,但会把时间戳的前4位置换为POSIX的UID或GID。这个版本的UUID在实际中较少用到。
name-based 基于名字的UUID(MD5)(版本 3)
基于名字的UUID通过计算名字和名字空间的MD5散列值得到。这个版本的UUID保证了:相同名字空间中不同名字生成的UUID的唯一性;不同名字空间中的UUID的唯一性;相同名字空间中相同名字的UUID重复生成是相同的。
randomly generated UUID 随机UUID(版本 4)
根据随机数,或者伪随机数生成UUID。这种UUID产生重复的概率是可以计算出来的,但随机的东西就像是买彩票:你指望它发财是不可能的,但狗屎运通常会在不经意中到来。
name-based 基于名字的UUID(SHA1)(版本 5)
和版本3的UUID算法类似,只是散列值计算使用SHA1(Secure Hash Algorithm 1)算法。
针对不同的应用,我们可以选择不同的UUID的版本。
JavaUUID提供了版本3、版本4的生成。
UUID方法
randomUUID
随机UUID生成
public static UUID randomUUID() {
//伪随机数生成器
SecureRandom ng = Holder.numberGenerator;
byte[] randomBytes = new byte[16];
//生成16字节的伪随机数
ng.nextBytes(randomBytes);
//第7个字节的高4位全部变0,低4位不变
randomBytes[6] &= 0x0f;
//第7个字节的高4位变0100,低4位不变,也就是设置版本号为4
randomBytes[6] |= 0x40;
//下面两步跟上面差不多,就是把变体所在字节变10xx,也就是变体2
randomBytes[8] &= 0x3f;
randomBytes[8] |= 0x80;
//然后调用私有构造器生成
return new UUID(randomBytes);
}
nameUUIDFromBytes
基于名字的UUID生成
public static UUID nameUUIDFromBytes(byte[] name) {
//信息摘要类,获取MD5,SHA1等算法
MessageDigest md;
try {
md = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException nsae) {
throw new InternalError("MD5 not supported", nsae);
}
//结合名字name,生成md5值,md5也是16字节
byte[] md5Bytes = md.digest(name);
//设置版本为3
md5Bytes[6] &= 0x0f;
md5Bytes[6] |= 0x30;
//设置变体为2 10xx
md5Bytes[8] &= 0x3f;
md5Bytes[8] |= 0x80;
//然后调用私有构造器生成
return new UUID(md5Bytes);
}
fromString
这个方法不是生成了,是用UUID的字符串(36个字符,包含-),生成UUID对象
public static UUID fromString(String name) {
String[] components = name.split("-");
if (components.length != 5)
throw new IllegalArgumentException("Invalid UUID string: "+name);
for (int i=0; i<5; i++)
components[i] = "0x"+components[i];
long mostSigBits = Long.decode(components[0]).longValue();
mostSigBits <<= 16;
mostSigBits |= Long.decode(components[1]).longValue();
mostSigBits <<= 16;
mostSigBits |= Long.decode(components[2]).longValue();
long leastSigBits = Long.decode(components[3]).longValue();
leastSigBits <<= 48;
leastSigBits |= Long.decode(components[4]).longValue();
return new UUID(mostSigBits, leastSigBits);
}
逻辑简单,通过字符“-”,将字符串spilt为字符串数组;然后数组中每个字符串都转成16进制,然后位运算构造UUID。
toString
将UUID输出为36个字符的字符串,可以结合前面的UUID示意图,理解起来就很简单了。
public String toString() {
return (digits(mostSigBits >> 32, 8) + "-" +
digits(mostSigBits >> 16, 4) + "-" +
digits(mostSigBits, 4) + "-" +
digits(leastSigBits >> 48, 4) + "-" +
digits(leastSigBits, 12));
}
0x yy 16进制中每两个字母表示1个字节;在我理解其实就是先把 64位转成16进制32个字符字符串,然后用“-”分割。
private static String digits(long val, int digits) {
long hi = 1L << (digits * 4);
return Long.toHexString(hi | (val & (hi - 1))).substring(1);
}
hashcode&equals
UUID重写了这两个方法
public int hashCode() {
//最高有效和最低有效的32位 做异或运算
long hilo = mostSigBits ^ leastSigBits;
//然后把异或运算的结果的 高位32和自己 再做异或运算(这样做就是hilo 高低位都参与了hash,减小了hash碰撞的概率)
return ((int)(hilo >> 32)) ^ (int) hilo;
}
public boolean equals(Object obj) {
if ((null == obj) || (obj.getClass() != UUID.class))
return false;
UUID id = (UUID)obj;
//最高有效和最低有效 值相等,则equals
return (mostSigBits == id.mostSigBits &&
leastSigBits == id.leastSigBits);
}
compareTo
两个UUID的比较,实现了Comparable接口
public int compareTo(UUID val) {
return (this.mostSigBits < val.mostSigBits ? -1 :
(this.mostSigBits > val.mostSigBits ? 1 :
(this.leastSigBits < val.leastSigBits ? -1 :
(this.leastSigBits > val.leastSigBits ? 1 :
0))));
}
基本逻辑就是先比较最高有效,在比较最低有效。
-1 表示 当前uuid 小于 val
0 表示 当前uuid 等于 val
1 表示 当前uuid 大于 val
UUID属性方法
获取最高有效字节
public long getLeastSignificantBits() {
return leastSigBits;
}
获取最低有效字节
public long getMostSignificantBits() {
return mostSigBits;
}
获取UUID版本
//返回结果,UUID版本,1,2,3,4;4个版本
public int version() {
//0x000000000000F000与运算
return (int)((mostSigBits >> 12) & 0x0f);
}
UUID : 5289df73-7df5-3326-bcdd-22597afb1fac
UUID Version : 3
获取变体
public int variant() {
return (int) ((leastSigBits >>> (64 - (leastSigBits >>> 62)))
& (leastSigBits >> 63));
}
参考变体定义类型,返回值有 {0,2,6,7}
0 = 0xxx
2 = 10xx
6 = 110x
7 = 111x
获取时间戳及时钟序列
只有版本1才有时间戳和时钟序列
public long timestamp() {
if (version() != 1) {
throw new UnsupportedOperationException("Not a time-based UUID");
}
return (mostSigBits & 0x0FFFL) << 48
| ((mostSigBits >> 16) & 0x0FFFFL) << 32
| mostSigBits >>> 32;
}
public int clockSequence() {
if (version() != 1) {
throw new UnsupportedOperationException("Not a time-based UUID");
}
return (int)((leastSigBits & 0x3FFF000000000000L) >>> 48);
}
获取MAC地址信息
只有版本1才有
public long node() {
if (version() != 1) {
throw new UnsupportedOperationException("Not a time-based UUID");
}
return leastSigBits & 0x0000FFFFFFFFFFFFL;
}
UUID就介绍到这里了,有什么问题,欢迎指正!
Java源码分析-UUID的更多相关文章
- Java源码分析 | CharSequence
本文基于 OracleJDK 11, HotSpot 虚拟机. CharSequence 定义 CharSequence 是 java.lang 包下的一个接口,是 char 值的可读序列, 即其本身 ...
- Java源码分析:关于 HashMap 1.8 的重大更新(转载)
http://blog.csdn.net/carson_ho/article/details/79373134 前言 HashMap 在 Java 和 Android 开发中非常常见 而HashMap ...
- JAVA源码分析-HashMap源码分析(二)
本文继续分析HashMap的源码.本文的重点是resize()方法和HashMap中其他的一些方法,希望各位提出宝贵的意见. 话不多说,咱们上源码. final Node<K,V>[] r ...
- Java源码分析之LinkedList
LinkedList与ArrayList正好相对,同样是List的实现类,都有增删改查等方法,但是实现方法跟后者有很大的区别. 先归纳一下LinkedList包含的API 1.构造函数: ①Linke ...
- Java源码分析:Guava之不可变集合ImmutableMap的源码分析
一.案例场景 遇到过这样的场景,在定义一个static修饰的Map时,使用了大量的put()方法赋值,就类似这样-- public static final Map<String,String& ...
- JAVA源码分析-HashMap源码分析(一)
一直以来,HashMap就是Java面试过程中的常客,不管是刚毕业的,还是工作了好多年的同学,在Java面试过程中,经常会被问到HashMap相关的一些问题,而且每次面试都被问到一些自己平时没有注意的 ...
- 【转】【java源码分析】Map中的hash算法分析
全网把Map中的hash()分析的最透彻的文章,别无二家. 2018年05月09日 09:08:08 阅读数:957 你知道HashMap中hash方法的具体实现吗?你知道HashTable.Conc ...
- 【Java源码分析】LinkedList类
LinkedList<E> 源码解读 继承AbstractSequentialList<E> 实现List<E>, Deque<E>, Cloneabl ...
- 一致性哈希Java源码分析
首次接触一致性哈希是在学习memcached的时候,为了解决分布式服务器的负载均衡或者说选路的问题,一致性哈希算法不仅能够使memcached服务器被选中的概率(数据分布)更加均匀,而且使得服务器的增 ...
随机推荐
- 如何设置输入IP地址就直接访问到某一个网站
如何设置输入IP地址就直接访问到某一个网站 1).在IIS中添加好站点后,在网站绑定中设置明确的IP地址,如下图: 2).修改Default WebSite的端口,或者是把Default WebSit ...
- VS2013 + Nunit 安装搭建
Nunit 官方给我提供了Nunit 3的四种安装方式 第一种 通过NuGet进行Full Nunit安装 第二种 通过NuGet进行轻量级 NunitLite 安装 第三种 通过Zip 压缩包下载安 ...
- 核发电站 (dp前缀优化)
大意: $n$个城市, $m$种核电站, 第$i$种假设要建在第$x$个城市, 必须满足$[x-i,x+i]$范围内无其他核电站, 求建核电站的方案数. 简单$dp$题, 设$dp[i][j]$为位置 ...
- Linux weblogic启停
一般weblogic启停在windows下很方便使用图标方式.但是在linux下需要杀掉weblogic进程才能真正关掉weblogic. 1.查询weblogic进程 ps -ef | grep & ...
- ADO与达梦7产生的一个未知问题
采用OLEDB与达梦7建立数据库连接 连接成功 查询表成功 打开表成功 当进行到addnew 操作时 报异常,未知错误 而且是仅针对这张表 ,其他表都没有问题 当清空数据后可以再插入一次数据,之后就 ...
- Qt使用自带的windeployqt 查找生成exe 必需的库文件
集成开发环境 QtCreator 目前生成图形界面程序 exe 大致可以分为两类:Qt Widgets Application 和 Qt Quick Application.下面分别介绍这两类exe ...
- C语言memset函数详解
C语言memset函数详解 memset() 的作用:在一段内存块中填充某个给定的值,通常用于数组初始化与数组清零. 它是直接操作内存空间,mem即“内存”(memory)的意思.该函数的原型为: # ...
- shell脚本——字符串
printf printf "%-10s %-10s %-10s\n" NO Name Height printf "%-10s %-10s %-10d\n&quo ...
- Miniconda虚拟环境管理工具命令方法
创建制定Python版本的虚拟环境 conda create --name 虚拟环境名称 Python=3.7.3(版本号) 进入指定虚拟环境 conda activate 虚拟环境名称 退出虚拟环境 ...
- CentOs Linux 对于编辑文本内容时无法退出的几个小命令
编辑完保存退出的四种方式 1. Esc+:+wq+回车(w是write,q是quit) 2. Esc+:+x+回车(x=wq) 3. Esc+shift+zz 4. Esc+ZZ(在大写开启下)