UUID的压缩

简介:

概述

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进制。
相关文章
从rtsp视频流中截取图片
从rtsp视频流中截取图片
2758 0
|
监控 Java 数据处理
【Spring云原生】Spring Batch:海量数据高并发任务处理!数据处理纵享新丝滑!事务管理机制+并行处理+实例应用讲解
【Spring云原生】Spring Batch:海量数据高并发任务处理!数据处理纵享新丝滑!事务管理机制+并行处理+实例应用讲解
|
Windows
如何使用挂载磁盘和windows服务器进行文件传输?
如何远程连接windows服务器,相信对于使用过windows服务器的朋友来说这都是非常简单的事情,但是对于如何以及为什么挂载本地磁盘到windows服务器,很多新手就不明白为什么了。
2304 1
|
弹性计算 算法 应用服务中间件
nginx配置访问密码,实现用户输入用户名密码才能访
如果我们在 nginx 下搭建了一些站点,但是由于站点内容或者流量的关系,我们并不想让所有人都能正常访问,那么我们可以设置访问认证。只有让用户输入正确的用户名和密码才能正常访问。效果如下:
4134 0
|
12月前
|
存储 缓存 安全
探索快手平台:如何通过官方API接口获取作品详细数据
本文介绍如何通过快手开放平台API获取短视频的播放、点赞、评论等详细数据,涵盖账号注册、应用创建、权限申请、接口调用及签名生成方法,并提供Python示例代码与注意事项,助力开发者合规高效地进行内容分析与运营优化。
2361 0
|
域名解析 缓存 网络协议
DNS更新后不生效?快速排查攻略
本文介绍了修改DNS后不生效,其主因是DNS传播延迟。TTL值、ISP缓存及服务器位置影响传播速度。提前调小TTL、清除本地缓存、更换公共DNS可加速。通过nslookup、Dig或Myssl工具可检测全球解析状态,确保更新完成。
2068 1
|
SQL 关系型数据库 MySQL
SQL如何对不同表的数据进行更新
本文介绍了如何将表A的Col1数据更新到表B的Col1中,分别提供了Microsoft SQL和MySQL的实现方法,并探讨了多表合并后更新的优化方式,如使用MERGE语句提升效率。适用于数据库数据同步与批量更新场景。
|
SQL 关系型数据库 MySQL
SQL中,可以使用 `ORDER BY` 子句来实现排序功能
【10月更文挑战第26天】SQL中,可以使用 `ORDER BY` 子句来实现排序功能
2036 6
|
JSON 小程序 前端开发
微信小程序的目录结构及页面结构的说明
本文详细介绍了微信小程序的目录结构、页面组成部分以及项目的全局配置文件,阐述了小程序的宿主环境和运行机制,包括小程序启动和页面渲染的过程。
微信小程序的目录结构及页面结构的说明
|
存储 NoSQL 算法
Redis地理散列GeoHash
GeoHash作为一种高效的地理位置编码算法,在Redis中得到了很好的支持。通过使用Redis的GeoHash命令,可以方便地进行地理位置的存储、查询和计算。GeoHash在位置存储、附近位置搜索、距离计算和实时定位等场景中有着广泛的应用。掌握GeoHash及其在Redis中的使用方法,可以极大地提高地理位置相关应用的开发效率和性能。
522 5

热门文章

最新文章