Version 1 UUIDVersion 4 UUIDNil/Empty UUID

Java UUID 指南

一、概述

UUID (通用唯一标识符),也称为 GUID(全局唯一标识符)表示一个 128 位长的值,对于所有实际用途都是唯一的。UUID 的标准表示使用十六进制数字(八位字节):

123e4567-e89b-12d3-a456-556642440000

UUID 由十六进制数字(每个 4 个字符)和 4 个“-”符号组成,其长度等于 36 个字符。

Nil UUID 是一种特殊形式的 UUID,其中所有位都设置为零。

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

二. UUID类

UUID 类有一个构造函数:

UUID uuid = new UUID(long mostSignificant64Bits, long leastSignificant64Bits);

如果我们想使用这个构造函数,我们需要提供两个 long 值。但是,它需要我们自己为 UUID 构建位模式。

为方便起见,可以使用三种静态方法来创建 UUID。

第一种方法从给定的字节数组创建版本 3 UUID:

UUID uuid = UUID.nameUUIDFromBytes(byte[] bytes);

其次,randomUUID()方法创建版本 4 UUID。这是创建 UUID 最方便的方法:

UUID uuid = UUID.randomUUID();

第三个静态方法返回给定 UUID 的字符串表示形式的 UUID 对象:

UUID uuid = UUID.fromString(String uuidHexDigitString);

现在让我们看看 UUID 的结构。

三、结构

让我们以 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”。“a” (=10xx) 的二进制等效项将变体显示为 2。

3.2. UUID 版本

B代表版本。提到的 UUID 中的版本( B的值)是 4。

Java 提供了获取 UUID 的变体和版本的方法:

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

这些是变体 2 UUID 的五个不同版本:基于时间 (UUIDv1)、DCE 安全 (UUIDv2)、基于名称(UUIDv3 和 UUIDv5)和随机(UUIDv4)。

Java 为 v3 和 v4 提供了一个实现,但也提供了一个构造函数来生成任何类型的 UUID:

UUID uuid = new UUID(long mostSigBits, long leastSigBits);

4. UUID 版本

4.1。版本 1

UUID 版本 1 基于当前时间戳,从 1582 年 10 月 15 日开始以 100 纳秒为单位测量,并与创建 UUID 的设备的 MAC 地址连接。

如果需要考虑隐私,也可以使用随机的 48 位数字而不是 MAC 地址生成 UUID 版本 1。在本文中,我们将研究这种替代方案。

首先,我们将生成 64 个最低和最高有效位作为 long 值:

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

private static long get64MostSignificantBitsForVersion1() {
    LocalDateTime start = LocalDateTime.of(1582, 10, 15, 0, 0, 0);
    Duration duration = Duration.between(start, LocalDateTime.now());
    long seconds = duration.getSeconds();
    long nanos = duration.getNano();
    long timeForUuidIn100Nanos = seconds * 10000000 + nanos * 100;
    long least12SignificatBitOfTime = (timeForUuidIn100Nanos & 0x000000000000FFFFL) >> 4;
    long version = 1 << 12;
    return 
      (timeForUuidIn100Nanos & 0xFFFFFFFFFFFF0000L) + version + least12SignificatBitOfTime;
}

然后我们可以将这两个值传递给 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

UUID 是使用命名空间和名称的哈希生成的。命名空间标识符是 UUID,如域名系统 (DNS)、对象标识符 (OID)、URL 等。

UUID = hash(NAMESPACE_IDENTIFIER + NAME)
UUIDv3 和 UUIDv5 之间的唯一区别是哈希算法——v3 使用 MD5(128 位),而 v5 使用 SHA-1(160 位)。

简单地说,我们将生成的哈希截断为 128 位,然后将 4 位替换为版本,2 位替换为变体。

让我们生成类型 3 UUID:

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

UUID uuid = UUID.nameUUIDFromBytes(result);

在这里,需要注意的是,命名空间的十六进制字符串首先需要转换为字节数组。

最后,Java 不提供类型 5 的实现。请查看我们的源代码库以获取 UUIDv5。

4.4. 版本 4

UUIDv4 实现使用随机数作为源。Java 实现是SecureRandom,它使用不可预测的值作为种子来生成随机数以减少冲突的机会。

让我们生成版本 4 UUID:

UUID uuid = UUID.randomUUID();

让我们使用“SHA-256”和随机 UUID 生成一个唯一密钥:

MessageDigest salt = MessageDigest.getInstance("SHA-256");
salt.update(UUID.randomUUID().toString().getBytes("UTF-8"));
String digest = bytesToHex(salt.digest());

5. 结论

在本文中,我们了解了 UUID 的结构以及存在哪些变体和版本。

我们还了解了 Java 为哪些版本提供了开箱即用的实现,并查看了代码示例以生成其他版本。