Java中的UUID指南

2025/03/17

1. 概述

在本文中,我们将了解UUID(通用唯一标识符)代码,有时也称为GUID(全局唯一标识符)。简而言之,它是一个以“-”分隔的十六进制字符形式的128位长数字:

e58ed763-928c-4155-bee9-fdbaaadc15f3

一个标准的UUID代码包含32个十六进制数字和4个“-”符号,这使得它的长度等于36个字符。还有一个Nil UUID代码,其中所有位都设置为0。

在这里,我们将看看Java中的UUID类。首先,我们将了解如何使用类本身,然后我们将看看不同类型的UUID以及我们如何在Java中生成它们。

2. UUID类

UUID类有一个构造函数,它需要两个long参数来描述最重要的64位和最不重要的64位:

UUID uuid = new UUID(mostSignificant64Bits, leastSignificant64Bits);

直接使用构造函数的缺点是我们必须构造UUID的位模式,当我们想重新创建一个UUID对象时,这可能是一个很好的解决方案。但大多数时候,我们使用UUID来标识一些东西,并且可以分配一个随机值。因此,UUID类提供了三个我们可以使用的静态方法。

首先,我们可以使用.nameUUIDFromBytes()方法创建一个版本3 UUID ,它需要一个字节数组作为参数:

UUID uuid = UUID.nameUUIDFromBytes(bytes);

其次,我们可以从先前生成的代码中解析UUID字符串值:

UUID uuid = UUID.fromString(uuidHexDigitString);

同样,此方法使用一些输入来创建UUID代码。但是,有一种更方便的方法可以在不提供任何参数作为输入的情况下创建UUID。最后,使用.randomUUID()方法,我们可以创建一个版本4 UUID:

UUID uuid = UUID.randomUUID();

接下来,我们将尝试了解UUID的结构。

3. 结构

特别是,让我们考虑以下带有相应掩码的UUID:

123e4567-e89b-42d3-a456-556642440000
xxxxxxxx-xxxx-Bxxx-Axxx-xxxxxxxxxxxx

3.1 UUID变体

在上面的示例中,A表示定义UUID布局的变体。UUID中的所有其他位取决于变量字段的布局。因此,变体表示A的三个最高有效位:

  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中的A值为“a”,二进制为10xx。因此,布局变体为2。

3.2 UUID版本

同样,B代表版本。在示例UUID中,B的值为4,这意味着它使用的是版本4。

对于Java中的任何UUID对象,我们可以使用.variant()和.version()方法检查变体和版本:

UUID uuid = UUID.randomUUID();
int variant = uuid.variant();
int version = uuid.version();

此外,变体2 UUID有五个不同的版本:

  • 基于时间(UUID v1)
  • DCE安全(UUID v2)
  • 基于名称(UUID v3和UUID v5)
  • 随机(UUID v4)

但是,Java只提供了v3和v4的实现。或者,我们可以使用构造函数生成其他类型。

4. UUID版本

4.1 版本1

UUID版本1使用当前时间戳和生成UUID的设备的MAC地址。特别是,时间戳是从1582年10月15日开始以100纳秒为单位测量的。不过,如果隐私是一个问题,我们可以使用一个随机的48位数字而不是MAC地址。

考虑到这一点,让我们生成最低有效位和最高有效位的64位作为long值:

private static long get64LeastSignificantBitsForVersion1() {
    Random random = new Random();
    long random63BitLong = random.nextLong() & 0x3FFFFFFFFFFFFFFFL;
    long variant3BitFlag = 0x8000000000000000L;
    return random63BitLong + variant3BitFlag;
}

上面,我们组合了两个long值,表示随机long值的最后63位和3位变体标志。接下来,我们使用时间戳创建64个最高有效位:

private static long get64MostSignificantBitsForVersion1() {
    final long timeForUuidIn100Nanos = System.currentTimeMillis();
    final long time_low = (timeForUuidIn100Nanos & 0x0000_0000_FFFF_FFFFL) << 32; 
    final long time_mid = ((timeForUuidIn100Nanos >> 32) & 0xFFFF) << 16;
    final long version = 1 << 12; final long time_hi = ((timeForUuidIn100Nanos >> 48) & 0x0FFF);
    return time_low + time_mid + version + time_hi;
}

然后我们可以将这两个值传递给UUID的构造函数:

public static UUID generateType1UUID() {
    long most64SigBits = get64MostSignificantBitsForVersion1();
    long least64SigBits = get64LeastSignificantBitsForVersion1();
    return new UUID(most64SigBits, least64SigBits);
}

4.2 版本2

接下来,版本2也使用时间戳和MAC地址。但是,RFC 4122并未指定确切的生成细节,因此我们不会在本文中查看实现。

4.3 版本3和5

版本3和版本版UUID使用从唯一名称空间中提取的哈希名称。此外,名称的概念不限于文本形式。例如,域名系统(DNS)、对象标识符(OID)、URL等都被视为有效的命名空间。

UUID = hash(NAMESPACE_IDENTIFIER + NAME)

详细来说,UUID v3和UUID v5之间的区别在于哈希算法-v3使用MD5(128位),而v5使用截断为128位的SHA-1(160位)。对于这两个版本,我们替换位以相应地更正版本和变体。

或者,我们可以从先前的命名空间和给定名称生成类型3 UUID,并使用方法.nameUUIDFromBytes():

byte[] nameSpaceBytes = bytesFromUUID(namespace);
byte[] nameBytes = name.getBytes("UTF-8");
byte[] result = joinBytes(nameSpaceBytes, nameBytes);

UUID uuid = UUID.nameUUIDFromBytes(result);

在这里,我们将命名空间的十六进制字符串转换为字节数组,然后将其与名称组合以创建UUID。

为了简单起见,我们不会描述版本5的实现,因为它是相似的。但是,请记住Java不实现类型5。

4.4 版本4

最后,我们已经描述了如何生成版本4 UUID。同样,我们调用UUID类提供的randomUUID()方法来获取UUID v4。

5. 总结

在本教程中,我们看到了UUID的结构和各种现有版本。首先,我们了解了如何在Java中创建UUID。然后,我们更详细地描述了一些UUID版本。最后,我们提供了一些代码示例来手动生成自定义需求的UUID代码。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章