UUID原理,以及JAVA生成短8位UUID

最近需要生成短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

https://blog.csdn.net/andy_miao858/article/details/9530245

  • 19
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 12
    评论
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值