在计算机中,整数值通常有两种类型:32位以及64位,在Java中分别对应的是int和long,在C++中分别对应的是int32/uint32和int64/uint64。整型值按照二进制补码的形式存放。而且是定长的。也就是不论数值大小,类型一旦固定,占用的bit位数就是固定的。
那么protobuf协议如何序列化一个整数值?
protobuf使用Varints编码格式来编码整数,这个协议最大的特点是变长编码。怎么理解?也就是一个整数值编码后的字节数是不确定的,数值越小,占用的字节数也越少,比如127以内的数值,只需要一个字节,数值越大占用的字节数也越大。Varints编码需要征用每一个字节的最高位作为标记为,简称MSB,所以一个字节实际上只有7个bit在实际编码数据。那这个MSB位的作用是啥?是用来表示是否还需要读取下一个字节:如果是0,表示不需要,否则表示需要。这个思路有点类似utf-8编码。另外,Varints是小端序,需要将字节翻转。
看几个例子:
7 = 00000111 -> 7,可以在7个bit内表示,共占用一个1字节;
320 = 256 + 64 = 101000000(补码表示) -> 0000010 1000000(7bit分组) -> 00000010 11000000(从右向左标记MSB) -> 11000000 00000010(字节翻转) -> -64 2
所以,我们可以看到7使用了1字节,320会用了2字节。在数值较小时,还是很省空间的。
如果是要表示负数呢?
我们知道负数在补码中首位会是连续的1,也就是任意一个负数,开始都会是连续的1。这样的格式在Varints编码下会是怎样的?
举个例子:
以Java里的int类型为例,-64 = 11111111 11111111 11111111 11000000
虽然绝对值很小,但是补码整整占用了4个字节,在转为Varints编码后需要5个字节才能表示,而且不论负数的数值多大,都稳定占用5个字节。(这里说明一下,实际上在protobuff中,负数会稳定占用10个字节,也就是会按照long来编码,这里为了简化,暂且以5个字节来说明。至于为啥是10个字节,是因为如果将int32改为了int64,仍然可以兼容)。
所以说,在表示负数时,Varints会退化为定长编码,不够高效。为了解决这个问题,引入了Zigzag编码,我们看一下:核心思路是将负数转化为其绝对值再进行Varints编码。怎么转化?0, -1, 1, -2对应为0, 1, 2, 3等等
对应关系如上,类似一个拉链,所以才叫Zigzag。
那如何知道一个字节流使用了Zigzag编码?
protobuff里引入了sint32/sint64类型,只有声明为这两个类型才会对负数先使用Zigzag编码,否则直接使用Varints编码。
接下来看一下几种整数类型的区别(https://developers.google.com/protocol-buffers/docs/proto?csw=1#scalar):
有符号的:int32/int64、sint32/sint64
无符号:uint32/uint64
在Java里,由于没有无符号整数,所以uint和int是一样的。在C++中会有区别。那这几个类别怎么选择?
如果确定没有负数,可以选择uint。在Java里,uint和int一样,所以选择两个都可以。
如果有负数,且出现频率较高,可以选择sint。否则选择int,毕竟sint还需要多一次编码。
另外,补码数值在转为字节流时,其实不关心是uint还是int,以及是int32还是int64,之所以需要区分这些类型,是因为需要在反序列时,知道转为对应语言下的哪一个类型。比如一个数值i=6,在int32和int64下序列化时一样的,同一个值需要反序列为int还是long,是根据pb里定义的类型来定的。
关于同一个值下的int32和int64序列化后的字节流一样的例子:
值为:8, 4, 16, 4。所以int的4和long的4,使用Varints序列化后的字节是一样的。int32和int64的却别在于反序列化时表示语言里的数据类型。
小结一下:
定长编码:一视同仁,所有数值都是相同空间,通过类型即可确定占用空间大小;变长编码:根据数值大小调整占用的空间,但是需要一个标识空间大小的信息,Varints是通过征用MSB位的方式;
Zigzag编码:Varints在表示负数时会退化为定长编码,引入了Zingzag及sintxxx,对绝对值Varins编码。如果负数较多,最好用sintxxx;
在Java里,intxxx和uintxxx一样,没有无符号整数。如果负数不多,用这俩就行,如果较多,最好用sintxxx;
参考链接:
https://www.jianshu.com/p/73c9ed3a4877
https://ngtzeyang94.medium.com/go-with-examples-protobuf-encoding-mechanics-54ceff48ebaa