UUID (通用唯一标识符),也称为 GUID(全局唯一标识符)表示一个 128 位长的值,对于所有实际用途都是唯一的。UUID 的标准表示使用十六进制数字(八位字节):
123e4567-e89b-12d3-a456-556642440000
UUID 由十六进制数字(每个 4 个字符)和 4 个“-”符号组成,其长度等于 36 个字符。
Nil UUID 是一种特殊形式的 UUID,其中所有位都设置为零。
在本教程中,我们将了解 Java 中的UUID类。首先,我们将了解如何使用类本身。然后我们将了解不同类型的 UUID 以及如何在 Java 中生成它们。
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
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。
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);
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);
}
版本 2 基于时间戳和 MAC 地址。但是,RFC 4122没有指定确切的生成细节,因此我们不会在本文中查看实现。
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。
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());
在本文中,我们了解了 UUID 的结构以及存在哪些变体和版本。
我们还了解了 Java 为哪些版本提供了开箱即用的实现,并查看了代码示例以生成其他版本。