2018/7/15 15:09:16当前位置媒体热门新闻热点浏览文章

概述

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进制。
网友评论