公众号:疾风追马
前言
有一次面试,面试官问我 UUID 会不会重复,我当时只知道 UUID 是通用唯一识别码,既然是唯一,那肯定就不会重复呀。事实证明当前的我还是太年轻了,今天就来一次讲清楚 UUID 的知识。
正文
什么是 UUID?
通用唯一识别码(英语:Universally Unique Identifier,缩写:UUID)是用于计算机体系中以识别信息的一个 128 位标识符。
UUID 按照标准方法生成时,在实际应用中具有唯一性,且不依赖中央机构的注册和分配。UUID 重复的概率接近零,可以忽略不计。
因此,所有人都可以自行建立和使用 UUID,而且几乎可以确定其不会与既有的标识符重复。也因为如此,在不同地方产生的 UUID 可以使用于同一个数据库或同一个频道中,而且几乎不可能重复。
UUID 的应用相当普遍,许多计算平台都提供了对于生成和解析 UUID 的支持。
这是百科里关于 UUID 的描述,大多数人对于 UUID 的理解可能也就到这里了,但是为什么是重复概率接近零,而不是绝对不会重复呢?
UUID 的版本
UUID 目前标准版中有 5 个版本,这里版本的意思,其实就是生成规则,不同版本生成规则不同。
版本 1 的 UUID 是根据时间和节点 ID(通常是 MAC 地址)生成;
版本 2 的 UUID 是根据标识符(通常是组或用户 ID)、时间和节点 ID 生成;
版本 3、版本 5 透过对命名空间(namespace)标识符和名称进行散列生成确定性的 UUID;
版本 4 的 UUID 则使用随机性或伪随机性生成。
如何识别 UUID 的版本
这是版本 4 的 UUID,第二个-后第一位就是他的版本号啦~
Java 中的 UUID
/**
* Static factory to retrieve a type 4 (pseudo randomly generated) UUID.
*
* The {@code UUID} is generated using a cryptographically strong pseudo
* random number generator.
*
* @return A randomly generated {@code UUID}
*/
public static UUID randomUUID() {
SecureRandom ng = Holder.numberGenerator;
byte[] randomBytes = new byte[16];
ng.nextBytes(randomBytes);
randomBytes[6] &= 0x0f; /* clear version */
randomBytes[6] |= 0x40; /* set to version 4 */
randomBytes[8] &= 0x3f; /* clear variant */
randomBytes[8] |= 0x80; /* set to IETF variant */
return new UUID(randomBytes);
}
/**
* Static factory to retrieve a type 3 (name based) {@code UUID} based on
* the specified byte array.
*
* @param name
* A byte array to be used to construct a {@code UUID}
*
* @return A {@code UUID} generated from the specified array
*/
public static UUID nameUUIDFromBytes(byte[] name) {
MessageDigest md;
try {
md = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException nsae) {
throw new InternalError("MD5 not supported", nsae);
}
byte[] md5Bytes = md.digest(name);
md5Bytes[6] &= 0x0f; /* clear version */
md5Bytes[6] |= 0x30; /* set to version 3 */
md5Bytes[8] &= 0x3f; /* clear variant */
md5Bytes[8] |= 0x80; /* set to IETF variant */
return new UUID(md5Bytes);
}
java 中的 UUID 类位于 java.util 包下
randomUUID 这个方法是类型 4 的随机数生成的,也是我们最常用的
nameUUIDFromBytes 这个则是类型 3 基于名称生成的
我们来通过代码验证一下
UUID uuid1 = UUID.nameUUIDFromBytes(new byte[]{'a', 'b', 'c'});
System.out.println("uuid1 = " + uuid1);
UUID uuid2 = UUID.nameUUIDFromBytes(new byte[]{'a', 'b', 'c'});
System.out.println("uuid2 = " + uuid2);
UUID uuid3 = UUID.randomUUID();
System.out.println("uuid3 = " + uuid3);
UUID uuid4 = UUID.randomUUID();
System.out.println("uuid4 = " + uuid4);
uuid1 = 90015098-3cd2-3fb0-9696-3f7d28e17f72
uuid2 = 90015098-3cd2-3fb0-9696-3f7d28e17f72
uuid3 = 2fd31523-bffb-4a7d-8cb9-a8b9c398822e
uuid4 = af06ce71-6da4-4fdc-bc2b-096c99dbaba5
用 nameUUIDFromBytes 这个方法,如果传入的字符数组相同,那么生成 UUID 也会相同
而用 randomUUID 这个方法则不会生成相同的 UUID
冲突概率
UUID 的标准型式包含 32 个 16 进制数字,以连字号分为五段,形式为 8-4-4-4-12 的 32 个字符。示例:550e8400-e29b-41d4-a716-446655440000
因为是 16 进制,每个位置有 16 种情况,共 32 个字符,理论上能生成不重复的结果总共有 16^32=2^128 种,约等于 3.4 x 10^38
这是一个非常非常大的数字,想想看,一亿也只是 10^8
我们再来计算冲突的概率
假设已经生成了 1 个 UUID,那么下一次生成发生冲突的概率是 1/3.4 x 10^38
假设已经生成了 2 个 UUID,那么下一次生成发生冲突的概率是 2/3.4 x 10^38
...
随着我们已经生成的 UUID 不断变多,下次发生冲突的概率也在不断变大,但是因为总量实在太大,所以尽管概率一直在变大,也可以完全忽略不计
总结
严谨来讲,UUID 可能会发生冲突,因为总量大,发生冲突的可能性极小,可以忽略不计
优点:
- 本地生成 ID,不需要进行远程调用,没有网络耗时
- 简单快速,没有性能上限
缺点:
- 可读性差
- 长度过长,生成的 UUID 通常是 36 位 (包含 -),不适合作为数据库主键,如果作为主键,二级索引 (非主键索引) 会占用很大的空间。
- 无法保证趋势递增,在 MySQL 的 InnoDB 引擎下,新插入数据会根据主键来寻找合适位置,会导致频繁的移动、分页增加了很多开销。
本文由mdnice多平台发布
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。