通用唯一识别码 (英语:Universally Unique Identifier,缩写:UUID )是用于计算机体系中以识别信息的一个128位标识符。
UUID 按照标准方法生成时,在实际应用中具有唯一性,且不依赖中央机构的注册和分配。UUID重复的概率接近零,可以忽略不计。
可以这样理解的是,UUID 就是一个可以在任何计算机中生成的一个识别码,这个识别码可以做到全球唯一,用来标识你的数据。
在本文章中,我们对 UUID 在 Java 中是如何实现来进行一些说明和解读。
结构
在对 UUID 进行说明之前,我们来看一个标准的 UUID。
下面就是一个标准的 UUID,使用横杠分隔符来进行分隔:
123e4567-e89b-42d3-a456-556642440000
xxxxxxxx-xxxx-Bxxx-Axxx-xxxxxxxxxxxx
标准的 UUID 目前是使用 32 个十六进制的字符来表示的。如果你对计算机中的 16 进制如何表示还不是非常熟悉的话,可以自行脑补下。
标准 UUID 使用横杠被分隔成了 5 组,分别为 8-4-4-4-12 个字符。总共这里有 32 个字符。因为我们使用了横杠,加上 4 个横杠后一共有 36 个字符。
Nil UUID 是一个特殊的 UUID,这个 UUID 中的所有字符都是 0,其值为:
00000000-0000-0000-0000-000000000000
针对 UUID 上面的结构,每一位都会有具体的意义:
具体的分配如下图:
对大部分人来说,我们值需要了解下就可以了,因为我们多会使用 API 来进行生成,如果我们不在这里说明的话,在后面我们说版本的时候就比较难理解。
变形
版本号在时间戳的最高 4 位(time_hi_and_version 的 4-7 bit)
UUID 的变体规则如下:
MSB1 MSB2 MSB3
0 X X reserved (0)
1 0 X current variant (2)
1 1 0 reserved for Microsoft (6)
1 1 1 reserved for future (7)
针对微软的版本,UUID 还单独为微软保留了一个版本,这就可以说明为什么在 Azure 的平台上,微软那么喜欢用 UUID。
版本
在实际使用的时候,我们对 UUID 的版本其实并不关心。
目前大部分情况下使用默认的版本进行编程就可以了。
- Version 1 (基于时间):这个版本是基于随机数的,使用的基数为每 100 纳秒为一个单位,时间的起点为1582年10月15日。同时还需要加上当前计算机的网卡物理地址(MAC)。
- Version 2 (DCE – 分布式计算机环境):
UUID-v2
和V1
很类似,是根据标识符(通常是组或用户ID
)、时间和节点ID
生成,不过区别在于V2
将V1
中的部分时间信息换成了主机名, 故应用具有局限性(有隐私风险),未大规模使用。 - Version 3 (基于命名):
UUID-v3
通过散列(MD5
)名字空间(namespace
)标识符和名称生成。和V1
、V2
不同,V3
不依赖与机器信息和时间信息, 但是V3
要求输入命名空间+名称,命名空间本身也是一个UUID
,用来标识应用环境,名称通常是用户账号、用户名之类的内容,通过命名空间+名称+三列算法算出UUID
。 - Version 4 (基于随机数):
UUID-v4
组成UUID v4
的位是随机生成的,没有固有逻辑(除了第三段首个数字,该数字标识版本号),不包含命名空间、设备信息、时间信息。 故,UUID-v4
最容易理解、应用也最为广泛。 - Version 5 (基于使用 SHA-1 的命名):
UUID-v5
和V3
类似,区别在于散列算法,使用了sha1
散列算法。
因为当前 MD5 的散列算法已经不推荐大规模使用了,所以如果你还需要使用 UUID V3 的话,会推荐使用 V5。
SHA-1 散列算法是当前被建议替代 MD5 使用的算法。
UUID 类
Java 已经在自己的 JDK 中集成了 UUID 的算法,你可以直接使用 UUID 的构造函数来生成一个随机的 UUID。
UUID 有一个单独的构造函数来为你创建 UUID。
UUID uuid = new UUID(long mostSignificant64Bits, long leastSignificant64Bits);
上面的构造函数需要你提供 2 个 long 类型的参数,在实际使用的时候这个就比较麻烦了。
为了简化使用,UUID 还提供了 3 个静态方法来让你更快的创建 UUID 对象。
使用下面的方法来创建一个 V3 版本的 UUID,这个时候你需要提供一个 buytes 的数组。
UUID uuid = UUID.nameUUIDFromBytes(byte[] bytes);
下面这个方法是我们用得最多的,大部分情况下我们都会使用下面的这个方法,这个方法将会生成一个 V4 的 UUID,你不需要为这个方法提供任何参数:
UUID uuid = UUID.randomUUID();
还有一个静态方法就是通过提供一个 UUID 字符串来生成一个新的 UUID。
UUID uuid = UUID.fromString(String uuidHexDigitString);
下面来让我们看下,如何不使用 JDK 中给定的 UUID 方法来实现我们需要的 UUID。
实现
针对 UUID 的实现,我们分成 2 类。
针对只需要 UUID 的情况,我们实现使用 UUIDv1 和 UUIDv4 通常是最优的选择。
如果你的计算机是基于相同名字的,那么你应该选择使用 UUIDv3 或者 UUIDv5。
因为,RFC 4122 的标准中没有针对 UUIDv2 进行说明,所以我们就不在这里对实现进行讨论的。
同时也是因为 UUIDv3 有很大的局限性,在实际使用情况下并没有被大规模推广,所以目前属于基本忽略不计的状态。
UUIDv1 和 UUIDv4
针对 UUIDv1 这个版本,如果你担心你的 MAC 地址被泄露的话,你可以使用一个随机 48-bit 数组来替换 MAC 地址。
首先,我们先生成第一个 64 的值,并返回 long :
private static long get64LeastSignificantBitsForVersion1() {
final long random63BitLong = new Random().nextLong() & 0x3FFFFFFFFFFFFFFFL;
long variant3BitFlag = 0x8000000000000000L;
return random63BitLong | variant3BitFlag;
}
private static long get64MostSignificantBitsForVersion1() {
final long currentTimeMillis = System.currentTimeMillis();
final long time_low = (currentTimeMillis & 0x0000_0000_FFFF_FFFFL) << 32;
final long time_mid = ((currentTimeMillis >> 32) & 0xFFFF) << 16;
final long version = 1 << 12;
final long time_high = ((currentTimeMillis >> 48) & 0x0FFF);
return time_low | time_mid | version | time_high;
}
在生成上面 2 个值之后,我们在使用的 JDK 的 UUID 算法来生成 generateType1UUID。
public static UUID generateType1UUID() {
long most64SigBits = get64MostSignificantBitsForVersion1();
long least64SigBits = get64LeastSignificantBitsForVersion1();
return new UUID(most64SigBits, least64SigBits);
}
下面我们来看看 UUIDv4 的生成算法,这个算法是我们目前最常用的算法了。
在这个算法中,JDK 实现了 SecureRandom 这个随机数生成器,通过这个函数通常用来降低可能出现重复的可能性。
UUID uuid = UUID.randomUUID();
上面的算法就是 JDK 中对 UUIDv4 实现的方法。
因为简单,所以被大规模使用。
UUIDv3 和 UUIDv5
这个 UUID 的生成使用的使用命名空间和名字的散列算法。
命名空间使用类似下面的定义 UUIDs like Domain Name System (DNS), Object Identifiers (OIDs), 和URLs。
让我们来看下这个算法的伪代码:
UUID = hash(NAMESPACE_IDENTIFIER + NAME)
UUIDv3 和 UUIDv5 的不同在于使用的散列算法的不同,UUIDv3 使用的是 MD5 (128 bits) 的散列算法, UUIDv5 使用的是 SHA-1 (160 bits) 的散列算法。
基于上面 2 个版本的实现我们就不再在这里具体讨论了,因为用得不多,可以在有时间的时候做下研究,在实际开发的时候可能不会被采用。
结论和总结
在本文章中,我们对 Java 实现的 UUID 算法进行了一些说明。
需要注意的时候,我们大部分情况使用的 UUID 版本是 UUIDv4,主要原因还是因为简单,适合使用。
针对一个相对独立的命名空间,可能会被要求使用 UUIDv3 或 UUIDv5,如果被要求必须使用 UUIDv3 或 UUIDv5 的话,请使用 UUIDv5。