Base 128 varint 变长编码
该算法主要目的是降低整数的存储空间,可以作为序列化编码的一种方案。目前google的protoBuff在处理整数时就采用了改方案。算法内容比较简单,先附上算法说明,之后会提供java代码实现。
算法内容:
对于正数M编码:
① 将M转成二进制B,擦除多余的零,保留最短的二进制格式,例如B=000101,则应处理为B=101;
② 对B从低位开始,以7位分组,不够高位填零,得B=b0 b1 b2 ....,再按组倒置为C=bn...b2 b1 b0;
③依次对C按组添加标识位S,格式为D=Snbn ... S2b2 S1b1 S0b0,S位长为1bit,取值0或1,取值规则,若Si后面有分组,则Si=1,否则Si=0;
④ D即为编码后的数据
对于负数M编码:
① 将M转成二进制B,计算机中是采用补码表示
② 对B从低位开始,以7位分组,不够高位填零,得B=b0 b1 b2 ....,再按组倒置为C=bn...b2 b1 b0;
③依次对C按组添加标识位S,格式为D=Snbn ... S2b2 S1b1 S0b0,S位长为1bit,取值0或1,取值规则,若Si后面有分组,则Si=1,否则Si=0;
④ D即为编码后的数据
解码为其反过程
Int型数字Java代码如下:(其他类型类似)
public byte[] encode(int a) {
int length = 5;
if (a > -1) {
length = 0;
do {
length++; // 计算正数编码空间
} while ((1 << length * 7 - 1) < a);
}
byte[] data = new byte[length];
byte temp;
for (int i = 0; i < length; i++) {
temp = (byte) (a >> (i * 7) & 0x7f);
if (i > 0) {
data[i - 1] |= 0x80;// i-1后有数据,标识位置1
}
data[i] = temp;
}
return data;
}
public int decode(byte[] data) {
int result = 0;
for (int i = 0; i < data.length; i++) {
result |= (data[i] & 0x7f) << (i * 7);
}
return result;
}
为什么会选择7位一组,而不是其他?
首先计算机存储空间最小单位为B,即8bit,这就决定每组的大小一定不能超过8,否则分组时,填充的0会很多。其次如果选择以小于7位分组,则编码后需要填充更多的标识位,所以7是一个不错的选择。
该编码的缺点也很明显,对于负数起不到压缩的效果反而导致空间占用增大了,那有什么好的解决方法吗?
拍脑袋想一个,可以添加数字类型位呀,0表示正数,1表示负数,这样负数就可以使用正数表示,空间压缩效果就可以提高了。但这个也有缺陷,就是在实际应用中会很别扭,用户在申明整数类型时还要知道,这个整数一定是正数或者一定是负数,当然我们也可以在程序内部解决,但为了这个强行搞出一个字节来标示类型有点不值当,那有其他好办法吗?
Google的protoBuff中提供了一种解决方案,Zigzag函数映射,先对整数进行处理,通过公式将其全部映射到正数空间,这样再使用Base 128 varint算法编码时,可以获的一个不错的压缩比。映射函数如下:
Zigzag(n) = (n << 1) ^ (n >> 31), n为有符号32时
Zigzag(n) = (n << 1) ^ (n >> 63), n为有符号64时