UUID的压缩

作者: 呼啸长风 | 来源:发表于2018-07-14 23:32 被阅读100次

概述

UUID,通用唯一识别码(Universally Unique Identifier)。
UUID的目的是让分布式系统中的所有元素都能有唯一的辨识信息,而不需要透过中央控制端来做辨识信息的指定。
UUID的标准型式包含32个16进制数字,以连字号分为五段,形式为8-4-4-4-12的32个字符。
示例:

550e8400-e29b-41d4-a716-446655440000

——以上内容摘自百度百科(维基百科也一样的-_-)

实现

UUID有很多实现版本,以下是JDK的一个实现:

    private static class Holder {
        static final SecureRandom numberGenerator = new SecureRandom();
    }

    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);
    }

用SecureRandom生成的16字节(128bit)随机数,用掩码打上版本和IETF标识。
实际有效随机位122位。关于冲突概率,可以参考笔者另一片文章,漫谈散列函数

特征

UUID的优点很明显:“分布式”、“唯一”。
这些优点使得UUID被广泛使用,尤其是分布式环境下。

然而其缺点也很明显:无序,长度较长。
这些缺点也极大地限制了其应用范围,比如数据表的主键,通常大家都不会用UUID。

但还是有不少地方用到UUID的:
有时候想给一个对象分配一个标识,但是该对象不好提取唯一特征,然后该环境下又不好统一分配,
这时候很自然就想到UUID了,UUID不需要以对象特征为参数,也不用担心重复(不是说不会重复,只是不用担心,就像不用担心天上掉下陨石砸到自己一样-_-)。

压缩

但是看着这个36个字节长度的UUID,总不自觉地会想有没有优化的余地。
16字节的信息,用16进制显示,有32个字符,加上分隔符,有36字节。
事实上,如果用base64编码这16个字节,可以压缩到22字节。

    public static byte[] hex2Bytes(String hex) {
        if (hex == null || hex.isEmpty()) {
            return new byte[0];
        }
        byte[] bytes = hex.getBytes();
        int n = bytes.length >> 1;
        byte[] buf = new byte[n];
        for (int i = 0; i < n; i++) {
            int index = i << 1;
            buf[i] = (byte) ((byte2Int(bytes[index]) << 4) | byte2Int(bytes[index + 1]));
        }
        return buf;
    }

    private static int byte2Int(byte b) {
        return (b <= '9') ? b - '0' : b - 'a' + 10;
    }

    public static String compressUUID(String uuid){
        String hex = uuid.replace("-", "");
        byte[] bytes = FormatUtils.hex2Bytes(hex);
        return new String(Base64.encode(bytes, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP));
    }

UUID压缩前后:

d44979db-5c64-40f1-b47e-e7f41c4be9e7
3dkJ2-z92fr9DuD9rNvp4A

36字节相对22字节,节约接近40%的长度,对于存储和传输而言,都是较大的提升;
虽然从可读性来说,UUID的可读性更好。
在权衡可读性和性能的时候,笔者通常的想法是,如果阅读和书写比较频繁,选择可读性较好的,如果一年不看几次,选择对机器友好的。
尤其是对于数据库存储这种情况,由于存在规模效应,显然压缩的版本更具性价比。

优化

如果需要压缩版本的UUID,调用JDK的UUID生成字符串,再处理成压缩版的UUID,显然“绕圈子”了。
我们可以仿照JDK的写法直接生成:

    public static String randomUUID() {
        byte[] bytes = new byte[15];
        Holder.numberGenerator.nextBytes(bytes);
        return Base64.encodeToString(bytes, Base64.URL_SAFE | Base64.NO_WRAP);
    }

15字节的随机数,120bit, 和JDK的randomUUID效用上是差不多,然后15是3的倍数,base64编码时不需要PADDING;
生成20字节的字符串(15 / 3 * 4), 相对UUID的36字节,节约近一半的空间。

其他

base64编码有一个逼死强迫症的特点:除了常规字符[A-Za-z0-9]之外,需要另外两个字符才能凑够64个字符。
于是,我们看到base64分化了两个版本,分别以 ['+', '/'] 和 ['-', '_'] 作为补充字符的两个版本。
其中,后者是URL_SAFE的版本,前者编码后可能会包含'/', 而'/'是URL的分隔符。
但无论哪个版本,对于URL而言,有非常规字符确实确实不是很“美观”。
于是,有人想出了base62编码。
base62编码,通常用来给long编码还好,用来编码任意字节数组的话,效率很低。
不过对于long来说,base62编码长度为11字节,而十六进制编码也只是16个字节,而且十六进制可读性更好。

简书的文章ID,十六进制,12字节(48bit)。



12字节的长度,可读性OK;48bit,取值范围有两百多万亿,够用。总的来说,是比较均衡的方案。
我很好奇是怎么构造的:
随机数?可能性不大。
自增序列?不太像。通常纯自增序列的ID长度不固定,如QQ号。

如果让我来写,有可能会混合多个因子来构造ID。
例如Twitter的Snowflake,混合了时间戳,机器ID和序列号。


计算机从16位寄存器,到32位,再到64位,就不往上涨了;
在当前的体系下,对于数据库存储而言,64bit的ID是最适合的。

总结

  • 尽量用整型的ID;
  • 如果要用UUID,尽量用压缩的版本;
  • MD5也是128bit, 作为字符串传输和存储时,base64编码要优于16进制。

相关文章

  • UUID压缩

    通常我们生成的uuid都是128位,以36/32位十六进制表示。长度过长,但真正要压缩它的原因是由于我方平台需要与...

  • UUID的压缩

    概述 UUID,通用唯一识别码(Universally Unique Identifier)。UUID的目的是让分...

  • 唯一字符串(uuid)

    import uuid import base64 uuid.uuid4().bytes+ uuid.uuid4(...

  • UUID——初识

    在线生成UUID:http://www.uuid.online/ 一、什么是UUID? UUID是Universa...

  • iOS crash解析

    查看uuid 查看dSYM文件的uuid 查看app的uuid 查看crash文件的uuid在crash文件中找到...

  • 16. 使用uuid库产生基于时间戳的全局唯一ID

    import uuid print str(uuid.uuid1()) uuid1(): 基于时间戳 由MAC地址...

  • nodejs 使用uuid

    安装 npm install uuid --save 使用 引入var uuid = require('uuid'...

  • 获取描述文件的uuid

    通过sh脚本获取到描述文件的uuid 运行终端输出uuid时,输入 成功获取到uuid,会有输出uuid,例子如下...

  • iOS UUID IDFA IDFV总结

    UUID:程序每次调用 [[NSUUID UUID] UUIDString]时,uuid 都会改变。 IDFA:广...

  • js常用函数

    1.uuid UUIDGenerator 生成 UUID。 使用cryptoAPI 生成 UUID, 符合RFC4...

网友评论

  • do2018:看了你好几篇文章,写得很到位。我是一名电子工程师,是否愿意交个朋友交流软硬件?一起研究。
    呼啸长风:可以啊,简书私信留联系方式?

本文标题:UUID的压缩

本文链接:https://www.haomeiwen.com/subject/smrypftx.html