1. 概述
UUID(Universally Unique Identifier),也称为GUID(Globally Unique Identifier),是一个128位的值,对于所有实际用途来说都是唯一的。与大多数其他编号方案不同,它们的唯一性不依赖于中央注册机构或生成它们的各方之间的协调。
在本教程中,我们将看到在Java中生成UUID标识符的两种不同实现方法。
2. 结构
让我们看一个示例UUID,然后是UUID的规范表示:
123e4567-e89b-42d3-a456-556642440000
xxxxxxxx-xxxx-Bxxx-Axxx-xxxxxxxxxxxx
标准表示由32个十六进制(base-16)数字组成,以连字符分隔的五组显示,形式为8-4-4-4-12,共36个字符(32个十六进制字符和4个连字符)。
Nil UUID是UUID的一种特殊形式,其中所有位都为0。
2.1 变体
在上面的标准表示中,A表示UUID变体,它决定了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。
2.2 版本
再看标准表示,B代表版本。版本字段包含一个描述给定UUID类型的值,上面示例UUID中的版本(B的值)是4。
UUID有五种不同的基本类型:
- 版本1(基于时间):基于当前时间戳,从1582年10月15日开始以100纳秒为单位测量,与创建UUID的设备的MAC地址连接。
- 版本2(DCE–分布式计算环境):使用当前时间以及本地计算机上网络接口的MAC地址(或节点)。此外,版本2 UUID将时间字段的低位部分替换为本地标识符,例如创建UUID的本地帐户的用户ID或组ID。
- 版本3(基于名称):UUID是使用命名空间和名称的哈希值生成的。命名空间标识符是UUID,如域名系统(DNS)、对象标识符(OID)和URL。
- 版本4(随机生成):在此版本中,UUID标识符是随机生成的,不包含有关它们创建时间或生成它们的机器的任何信息。
- 版本5(使用SHA-1基于名称):使用与版本3相同的方法生成,但哈希算法有所不同。此版本使用命名空间标识符和名称的SHA-1(160位)哈希。
3. UUID类
Java有一个内置的实现来管理UUID标识符,无论我们是想随机生成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的一些实现。
4. 实现
我们将根据要求将实现分为两类,第一类是只需要唯一的标识符,为此,UUID v1和UUID v4是最佳选择。在第二类中,如果我们需要始终从给定名称生成相同的UUID,我们将需要UUID v3或UUID v5。
由于RFC 4122未指定确切的生成细节,因此我们不会在本文中查看UUID v2的实现。
现在让我们看看我们提到的类别的实现。
4.1 版本1和4
首先,如果隐私是一个问题,UUID v1也可以用一个随机的48位数字而不是MAC地址生成。在本文中,我们将研究这种替代方案。
首先,我们将生成64个最低和最高有效位作为long值:
private static long get64LeastSignificantBitsForVersion1() {
long random63BitLong = new 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 least12SignificantBitOfTime = (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);
}
我们现在将看到如何生成UUID v4,该实现使用随机数作为来源。Java实现是SecureRandom,它使用不可预测的值作为种子来生成随机数,以减少发生冲突的机会。
让我们生成一个版本4 UUID:
UUID uuid = UUID.randomUUID();
然后,让我们使用“SHA-256”和随机UUID生成一个唯一密钥:
MessageDigest salt = MessageDigest.getInstance("SHA-256");
salt.update(UUID.randomUUID().toString().getBytes(StandardCharsets.UTF_8));
String digest = bytesToHex(salt.digest());
4.2 版本3和5
UUID是使用命名空间和名称的哈希值生成的。命名空间标识符是UUID,如域名系统(DNS)、对象标识符(OID)和URL。我们看一下算法的伪代码:
UUID = hash(NAMESPACE_IDENTIFIER + NAME)
UUID v3和UUID v5之间的唯一区别是哈希算法—v3 使用MD5(128位),而v5使用SHA-1(160位)。
对于UUID v3,我们将使用UUID类中的方法nameUUIDFromBytes(String namespace, String name),它接收字节数组并应用MD5哈希。
因此,让我们首先从命名空间和特定名称中提取字节表示,并将它们连接到一个数组中以将其发送到UUID api:
byte[] nameSpaceBytes = bytesFromUUID(namespace);
byte[] nameBytes = name.getBytes(StandardCharsets.UTF_8);
byte[] result = joinBytes(nameSpaceBytes, nameBytes);
最后一步是将从上一个过程中获得的结果传递给nameUUIDFromBytes()方法,此方法还将设置变体和版本字段:
UUID uuid = UUID.nameUUIDFromBytes(result);
现在让我们看看UUID v5的实现,重要的是要注意Java不提供生成版本5的内置实现。
让我们检查代码以生成最低和最高有效位,同样作为long值:
private static long getLeastAndMostSignificantBitsVersion5(final byte[] src, final int offset) {
long ans = 0;
for (int i = offset + 7; i >= offset; i -= 1) {
ans <<= 8;
ans |= src[i] & 0xffL;
}
return ans;
}
现在,我们需要定义使用名称生成UUID的方法,此方法将使用UUID类中定义的默认构造函数:
public static UUID generateType5UUID(String name) {
try {
byte[] bytes = name.getBytes(StandardCharsets.UTF_8);
MessageDigest md = MessageDigest.getInstance("SHA-1");
byte[] hash = md.digest(bytes);
long msb = getLeastAndMostSignificantBitsVersion5(hash, 0);
long lsb = getLeastAndMostSignificantBitsVersion5(hash, 8);
// Set the version field
msb &= ~(0xfL << 12);
msb |= 5L << 12;
// Set the variant field to 2
lsb &= ~(0x3L << 62);
lsb |= 2L << 62;
return new UUID(msb, lsb);
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
}
}
5. 总结
在本文中,我们了解了有关UUID标识符的主要概念以及如何使用内置类生成它们。然后,我们看到了针对不同版本的UUID及其应用范围的一些高效实现。
Post Directory
