概述
谷歌的Protocol Buffers实现了一种名为varint的可变长度整数编码。
现在我们来看一下Varint编码是如何对整数进行变长编码的。
编码过程就是将证书转换为字节数组的过程,在计算机中通用的整数的字节数一般为1,2,4,8四种,分别表示代表int8,int16,int32和int64,如果再加上无符号的话就有int8/uint8,int16/uint16,int32/uint32,int64/uint64。
int8
int16
int32
int64
uint8
uint16
uint32
uint64
原理
Varint编码基本原理是将整数以7位为一组对整数进行拆分,每组再补充1位作为是否后面还有字节的标识。补充的这1位如果为1则表示还有,0为没有,没有就表示是一个完整的数字字节数了。
Varint编码最小字节为1个即8位,最大为10个字节。
整数与最大字节数对应如下:
编码
Varint编码算法在编码时会以7位为一个单位,判断字节的最高位是否为0,为0和不为0的处理如下:
- 如果为0,那么表示后续没有字节
- 如果为1,那么表示后续还有字节
怎么理解?
我们来举几个例子。
例1: 1个字节的整数:int8与uint8, 根据两个类型的范围我们来选择两个数字,我们先不涉及负数,因为负数是有符号的,有符号的需要使用Zigzag编码。
int8选择127, uint8选择128,接下来我们来看下varint编码后两个数字会是什么二进制数据。
-
127 编码(int8)
-
128 编码(uint8)
-
32767 编码(int32)
解码
解码就是编码的逆过程。
代码实现
编码
// PutUvarint encodes a uint64 into buf and returns the number of bytes written.
// If the buffer is too small, PutUvarint will panic.
func PutUvarint(buf []byte, x uint64) int {
i := 0
for x >= 0x80 {
buf[i] = byte(x) | 0x80
x >>= 7
i++
}
buf[i] = byte(x)
return i + 1
}
解码
// ReadUvarint reads an encoded unsigned integer from r and returns it as a uint64.
// The error is EOF only if no bytes were read.
// If an EOF happens after reading some but not all the bytes,
// ReadUvarint returns io.ErrUnexpectedEOF.
func ReadUvarint(r io.ByteReader) (uint64, error) {
var x uint64
var s uint
for i := 0; i < MaxVarintLen64; i++ {
b, err := r.ReadByte()
if err != nil {
if i > 0 && err == io.EOF {
err = io.ErrUnexpectedEOF
}
return x, err
}
if b < 0x80 {
if i == MaxVarintLen64-1 && b > 1 {
return x, errOverflow
}
return x | uint64(b)<<s, nil
}
x |= uint64(b&0x7f) << s
s += 7
}
return x, errOverflow
}
优劣
在Google的Protocol Buffers等序列化数据格式中,varints(variable length integers,可变长度整数)被广泛应用,主要原因是它能够更好地优化存储空间和传输效率。不过,这种方案也有其优点和缺点。
优势
-
空间优化。对于较小的数,varints可以用更少的字节来表示,从而节省了存储和传输的空间。
-
计算资源优化。对于大型数据集,由于使用更少的字节来表示小整数,这意味着需要更少的计算资源来处理和传输数据。
劣势
-
运算复杂度较高。由于varints的存储大小是可变的,所以需要更多的计算才能确定一共需要读取和写入多少个字节。
-
无法直接反向查找。普通整数可以直接通过偏移量查找,但是由于varints的可变长度,很难直接定位到特定位置的整数。
-
对于较大的数,varints可能需要使用更多的字节来表示。虽然经常需要处理的数值大多数情况下并不大,但一旦碰到需要处理较大的数,使用varints可能会比直接使用普通整数需要更多的空间。
总结
Varint编解码使用的变长编码方式为我们提供了更加紧凑的编码,Varint可以对无符号整型进行编码不能对有符号整型编码,同时增加了解码复杂度。