最近需要生成短uuid,网上查了查资料,这里整理记录一下,供大家参考
1 前言
UUID,全名叫做 Universally Unique Identifier,也就是通用唯一标识符的意思。有时候,也叫做全局唯一标识符,英文全名叫做 Globally Unique Identifier,简拼为 GUID。
大家应该都知道,用UUID.randomUUID()获取36位无重复的uuid。
来看一下 UUID 的格式:
123e4567-e89b-12d3-a456-556642440000
xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx
UUID的标准型式包含32个16进位数字,以连字号分为五段,形式为8-4-4-4-12的32个字符,加上“-”一共是36位,所以咱们可以先取出uuid,再把“-”去掉。
2 介绍
首先,即便是虚拟机的话MAC地址也是不一样的。另外你说的统一时间还是个宏观的概念,这个仅仅是决定了UUID生产串中的某一部分相同而已,因为为了保证UUID的唯一性,规范定义了包括网卡MAC地址、时间戳、名字空间(Namespace)、随机或伪随机数、时序等元素。
当然,你要说UUID是不是绝对的不会出现重复的,这个也不能这样说的(我下面会提到)。
2.1 UUID具有以下涵义:
经由一定的算法机器生成
为了保证UUID的唯一性,规范定义了包括网卡MAC地址、时间戳、名字空间(Namespace)、随机或伪随机数、时序等元素,以及从这些元素生成UUID的算法。UUID的复杂特性在保证了其唯一性的同时,意味着只能由计算机生成。
非人工指定,非人工识别
UUID是不能人工指定的,除非你冒着UUID重复的风险。UUID的复杂性决定了“一般人“不能直接从一个UUID知道哪个对象和它关联。
在特定的范围内重复的可能性极小
UUID的生成规范定义的算法主要目的就是要保证其唯一性。但这个唯一性是有限的,只在特定的范围内才能得到保证,这和UUID的类型有关(参见UUID的版本)。
2.2 UUID的版本
UUID具有多个版本,每个版本的算法不同,应用范围也不同。
首先是一个特例--Nil UUID--通常我们不会用到它,它是由全为0的数字组成,如下:
00000000-0000-0000-0000-000000000000
来看一下 UUID 的格式:
123e4567-e89b-12d3-a456-556642440000
xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx
复制代码
由四个中划线“-”隔开,第一部分的长度为 8,第二部分和第三部分的长度为 4,第四部分的长度为 12,总长度为 36,是固定的。每一部分都是一个十六进制的数字,注意并不是随机的任意字母+数字的字符串。
M 表示 UUID 的版本,N 为 UUID 的变体(Variants)。
2.2.1 版本
M 的值有 5 个可选项:
-
版本 1:UUID 是根据时间和 MAC 地址生成的;
-
版本 2:UUID 是根据标识符(通常是组或用户 ID)、时间和节点 ID生成的;
-
版本 3:UUID 是通过散列(MD5 作为散列算法)名字空间(namespace)标识符和名称生成的;
-
版本 4 - UUID 使用随机性或伪随机性生成;
-
版本 5 类似于版本 3(SHA1 作为散列算法)。
UUID Version 1:基于时间的UUID
基于时间的UUID通过计算当前时间戳、随机数和机器MAC地址得到。由于在算法中使用了MAC地址,这个版本的UUID可以保证在全球范围的唯一性。但与此同时,使用MAC地址会带来安全性问题,这就是这个版本UUID受到批评的地方。如果应用只是在局域网中使用,也可以使用退化的算法,以IP地址来代替MAC地址--Java的UUID往往是这样实现的(当然也考虑了获取MAC的难度)。
UUID Version 2:DCE安全的UUID
DCE(Distributed Computing Environment)安全的UUID和基于时间的UUID算法相同,但会把时间戳的前4位置换为POSIX的UID或GID。这个版本的UUID在实际中较少用到。
UUID Version 3:基于名字的UUID(MD5)
基于名字的UUID通过计算名字和名字空间的MD5散列值得到。这个版本的UUID保证了:相同名字空间中不同名字生成的UUID的唯一性;不同名字空间中的UUID的唯一性;相同名字空间中相同名字的UUID重复生成是相同的。
UUID Version 4:随机UUID
根据随机数,或者伪随机数生成UUID。这种UUID产生重复的概率是可以计算出来的,但随机的东西就像是买彩票:你指望它发财是不可能的,但狗屎运通常会在不经意中到来。
UUID Version 5:基于名字的UUID(SHA1)
和版本3的UUID算法类似,只是散列值计算使用SHA1(Secure Hash Algorithm 1)算法。
2.2.2 变体
为了能兼容过去的 UUID,以及应对未来的变化,因此有了变体(Variants)这一概念。
目前已知的变体有下面 4 种:
-
变体 0:格式为 0xxx,为了向后兼容预留。
-
变体 1:格式为 10xx,当前正在使用的。
-
变体 2:格式为 11xx,为早期微软的 GUID 预留。
-
变体 3:格式为 111x,为将来的扩展预留,目前暂未使用。
在上例中,M 是 1,N 是 a(二进制为 1010,符合 10xx 的格式),这就意味着这个 UUID 是“版本 1”、“变体 1”的 UUID。
目前大多数使用的 UUID 大都是变体 1,N 的取值是 8、9、a、b 中的一个。
2.3 UUID的应用
从UUID的不同版本可以看出,
Version 1/2适合应用于分布式计算环境下,具有高度的唯一性;
Version 3/5适合于一定范围内名字唯一,且需要或可能会重复生成UUID的环境下;
至于Version 4,个人的建议是最好不用(虽然它是最简单最方便的)。
通常我们建议使用UUID来标识对象或持久化数据,但以下情况最好不使用UUID:
映射类型的对象。比如只有代码及名称的代码表。
人工维护的非系统生成对象。比如系统中的部分基础数据。
对于具有名称不可重复的自然特性的对象,最好使用Version 3/5的UUID。比如系统中的用户。如果用户的UUID是Version 1的,如果你不小心删除了再重建用户,你会发现人还是那个人,用户已经不是那个用户了。(虽然标记为删除状态也是一种解决方案,但会带来实现上的复杂性。)
3 生成短uuid方式1
3.1 思路
短8位UUID思想其实借鉴微博短域名的生成方式,但是其重复概率过高,而且每次生成4个,需要随即选取一个。
本算法利用62个可打印字符,通过随机生成32位UUID,由于UUID都为十六进制,所以将UUID分成8组,每4个为一组,然后通过模62操作,结果作为索引取出字符,
这样重复率大大降低。
经测试,在生成一千万个数据也没有出现重复,完全满足大部分需求。
3.2 代码
代码贴出来供大家参考。
public static String[] chars = new String[] { "a", "b", "c", "d", "e", "f",
"g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s",
"t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5",
"6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I",
"J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V",
"W", "X", "Y", "Z" };
public static String generateTicket() {
String ticket = UUID.randomUUID().toString();
return ticket.replaceAll("-", "");
}
public static String generateShortUuid() {
//调用Java提供的生成随机字符串的对象:32位,十六进制,中间包含-
StringBuffer shortBuffer = new StringBuffer();
String uuid = UUID.randomUUID().toString().replace("-", "");
for (int i = 0; i < 8; i++) { //分为8组
String str = uuid.substring(i * 4, i * 4 + 4); //每组4位
int x = Integer.parseInt(str, 16); //将4位str转化为int 16进制下的表示
//用该16进制数取模62(十六进制表示为314(14即E)),结果作为索引取出字符
shortBuffer.append(chars[x % 0x3E]);
}
return shortBuffer.toString();
}
python
import uuid
array = ["a", "b", "c", "d", "e", "f",
"g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s",
"t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5",
"6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I",
"J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V",
"W", "X", "Y", "Z"]
def get_short_id():
id = str(uuid.uuid4()).replace("-", '')
buffer = []
for i in range(0, 8):
start = i * 4
end = i * 4 + 4
val = int(id[start:end], 16)
buffer.append(array[val b])
return "".join(buffer)
for i in range(100):
print(get_short_id())
另外:
8位uuid重复是必然的,毕竟长度只有8位,所需要的不重复uuid就需要增加长度。
4 生成短uuid方式2
java生成uuid是36位的,觉得太长肿么办,我们来把它变成22位的,uuid是有128位2进制,然后转化为32位16进制,在这个基础上在添加4个-,也就是把128位,每4位二进制数用0-9和a-f替换掉,那我们每6位二进制数转化 成a-z,A-Z,0-9加上-_刚好也能全部表示,我们把前面120位二进制数转化成20个字符,而后面八个二进制和原来一样转化为十六进制,这样就刚好22个字符,附上代码
public class UUID22 {
public static String[] chars = new String[] { "a", "b", "c", "d", "e", "f",
"g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s",
"t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5",
"6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I",
"J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V",
"W", "X", "Y", "Z", "-", "_" };
public static String generateShortUuid() {
StringBuffer shortBuffer = new StringBuffer();
String uuid = UUID.randomUUID().toString().replace("-", "");
// 每3个十六进制字符转换成为2个字符
for (int i = 0; i < 10; i++) {
String str = uuid.substring(i * 3, i * 3 + 3);
int x = Integer.parseInt(str, 16); //转成十六进制
shortBuffer.append(chars[x / 0x40]); //除64得到前面6个二进制数的
shortBuffer.append(chars[x % 0x40]); //对64求余得到后面6个二进制数1
}
//加上后面两个没有改动的
shortBuffer.append(uuid.charAt(30));
shortBuffer.append(uuid.charAt(31));
return shortBuffer.toString();
}
}
5 参考
https://juejin.cn/post/6856187633660493832
https://www.zhihu.com/question/34876910
https://blog.csdn.net/lizhengyu891231/article/details/91877403