简言
1. varint即 variable int,也就是变长的整型,在mysql,levelDB,protobuf中都有使用
2. varint编码的优点是对数值较小的数进行编码后占用字节较少。而我们常用的int32,int64之类,无论数字多大,都固定占4字节,8字节
3. varint编码的缺点是对数值较大的数和负数进行编码后占用字节较多,最多可能要占10个字节(表示负数的时候)
4. 因为生活中真实用到的数字往往都不大且是正数,所以综合优点缺点varint还是能节省一些空间
编码原理
varint 对数字的二进制,从右往左,每7位分割成一块
如果不是最后一块,则最左边补1,组成一个字节
如果是最后一块,则左边缺的全部补0,组成1个字节
最终保存的时候,根据分割块的顺序依次从左往右填
解码原理
读取字节流后,从左往右,逐个字节判断
如果该字节最左边为1,说明varint值没读取完,后面的字节还是本varint的值,继续读。本字节最左边的1去掉后,剩下的7位留作备用
如果该字节最左边为0,说明varint值读取完毕
最终,根据分割块的顺序依次从右往左填,组成的字节流按int型解析即可
举例(为了方便大家看,从右往左,每7位一个颜色)
测试用例1
数字5,二进制101
编码过程:
1. 从右往左依次取7位,第一次尝试取7位时,由于只有3位,所以只分割出来1块,也就是最后一块,根据规则前面补0,得到00000101
所以,varint表示法是00000101
跟原来二进制一样,所以[0~255]之间的数字只占用1个字节
解码过程:
1. 从左往右,逐个字节判断,由于只有1个字节,且最高位为0,说明varint值已读取完毕,所以最终的顺序也就是00000101,按int型解析,即5
测试用例2
数字130,按无符号,二进制10000010
编码过程:
1. 从右往左依次取7位,第一次取出来的0000010,因为前面还有数字1,说明自己不是最后一块,根据规则左边补1,得到 10000010
2. 第二次尝试取7位,只取出来1,因为前面已经没数字了,这里是最后一块了,根据规则前面补0,得到00000001
把这些分割后新组成的块,从左往右填
所以,varint表示法是10000010 00000001
解码过程:
1. 从左往右,逐个字节判断,第一个字节10000010最高位为1,说明后面字节还有,需要继续读。这里去掉最左边的标志1,剩下的0000010备用
2. 读第二个字节 00000001最高位为0,说明后面没了,本字节就是varint的最后一个字节了。去掉最左边的标志0,剩下的0000001备用
3. 根据分割块的顺序,从右往左填,组成的字节流00000010000010,去掉前面无意义的0,剩下10000010,根据无符号int型解析,得到130
测试用例3
数字12345678,二进制101111000110000101001110
编码过程:
1. 从右往左依次取7位,第一次取出来的1001110,因为自己不是最后一块,根据规则左边补1,得到11001110
2. 第二次取出来的1000010,因为自己不是最后一块,根据规则左边补1,得到11000010
3. 第三次取出来的1110001,因为自己不是最后一块,根据规则左边补1,得到11110001
4. 第四次取出来的101,因为自己是最后一块,根据规则左边补0,得到00000101
把这些分割后新组成的块,从左往右填
所以,最终varint表示法是11001110 11000010 11110001 00000101
解码过程:
1. 从左往右,逐个字节判断,第一个字节11001110最高位为1,说明后面字节还有,需继续读。
这里去掉最左边的1,剩下的1001110备用
2. 读第二个字节11000010 最高位为1,说明后面字节还有,需继续读。这里去掉最左边的1,剩下的1000010备用
3. 读第三个字节11110001 最高位为1,说明后面字节还有,需继续读。这里去掉最左边的1,剩下的1110001备用
4. 读第四个字节00000101 最高位为0,说明后面没了,本字节就是varint的最后一个字节了。去掉最左边的标志0,剩下的0000101备用
5. 根据分割块的顺序,从右往左填,组成的字节流0000101111000110000101001110
去掉前面无意义的0,剩下101111000110000101001110,按int型解析得到12345678
这篇博客我们讲了varint的实现原理
以正数举了3个例子,给大家分析了正数的编码、解码过程,下一篇我们讲varint对负数的处理