背景:数据库的前期设计,主键用的是 uuid,但这个是大数据量的应用。经过 n 久的折腾,数据大于 1 亿条了。返回去看表,发现,表的很多字段是 varchar2 的,但是长度不超过 20 字符。占据大部分空间的居然是 uuid。于是萌生改造 uuid 的想法。
大概思路:uuid 用 64 进制改造,把 uuid 生成的字符串去掉 “-”,再补一个 “0”,得到 33 位的 16 进制数,再用 22 个 64 进制数表示。利用 uuid 生成的 mostSigBits、leastSigBits 来做位移,再通过 base64 的算法将 16 字节的 2 个 long 类型转换成字符。
核心过程:
- 把字符串用 md5 转为 16 个字节,然后生成 UUID,UUID 里有 msb,lsb 两个 64bit 的 long 类型数字
- 把 lsb|msb 当做一个 128bit 的数字
- 最终看是要生成 22 位字符还是 20 位字符,用 64 进制表示,映射到 64 个字符上
- 如果是 22 位,就是 22*6 = 12bit0 lsb 前 8 位 4bit0 lsb 后 56 位 msb = 0000 0000 0000 8bit 0000 24bit 24bit 24bit (8+16) 24bit 24bit
- 如果是 20 位(es 生成的 id 就是 20 位),就是 20*6 =120 = lsb 后 56 位 msb,简单点就是 lsb 前 8 位不要了,或者这 8 位可以做点位操作,比如异或之类的,这里代码不涉及
出版代码写法是 24bit 24bit = 000000 000000 000000 000000 000000 000000 000000 000000 = out [4] out [5] out [6] out [7] out [0] out [1] out [2] out [3],这样写代码就很难写
现在的写法是 24bit 24bit = 000000 000000 000000 000000 000000 000000 000000 000000 = out [7] out [6] out [5] out [4] out [3] out [2] out [1] out [0],也就是按顺序,每 6bit 放到一个 out 里面,看着就舒服多了,效果一样的,只是字符顺序换了一下,写代码更简洁。
说完原理上代码
public static String getUUID(String str, int size) {
UUID uuid = UUID.nameUUIDFromBytes(str.getBytes());
long msb = uuid.getMostSignificantBits();
long lsb = uuid.getLeastSignificantBits();
char[] out = new char[size];
// 0000 0000 0000 8bit 0000 24bit 24bit 24bit(8+16) 24bit 24bit
long tmp = msb;
int idx = 0;
int value;
while (tmp != 0) {
value = (int) tmp & 0xffffff;
for (int i = 0; i < 4; i++) {
out[idx++] = alphabet[value & 0x3f];
value >>= 6;
}
// msb不够用了,补上lsb的48位
if (tmp >>> 24 == 0 && lsb >>> 48 != 0) {
tmp = lsb << 16 | tmp;
lsb = lsb >>> 48;
}
tmp = tmp >>> 24;
}
if (size == 22) {
value = (int) (lsb << 4);
out[idx++] = alphabet[value & 0x3f];
value >>= 6;
out[idx] = alphabet[value & 0x3f];
}
return new String(out);
}
public static void main(String[] args) {
String str = "this is a test string";
System.out.println(getUUID(str, 20));
System.out.println(getUUID(str, 22));
}