protocol buffers是google推出的一种数据序列化格式,简称protobuf。
具有以下优点
同时,也有以下缺点:
在项目 thrift-protobuf-compare中,对各种序列化方式做了全方位的对比,对比结果中,protobuf各项都处于第5左右(共21个),综合性能较好。
下图(左JSON,右Protobuf)是同样的一段数据,用json和protobuf分别描述(仅表示描述方式,并不是最终生成的序列化数据)。可以看出,protobuf是把json中的key去掉了,用数字代替key,从而实现了减小了序列化后的数据大小。而protobuf反序列化过程中,无需做字符串解析,所以速度也很快,综合性能优于json很多。
protobuf使用,主要分为以下步骤:
1. 定义消息格式,编写.proto文件
2. 选择合适的protobuf实现框架,对.proto文件进行编译,生成对应的源代码文件
3. 在代码中调用生成的源代码文件,完成序列化和反序列化功能
目前在Android种有两种实现框架,google-protobuf和square-wire。
1. 使用google-protobuf框架
以后补充
2. 使用square-wire框架
以后补充
如下图所示,protobuf数据是连续的key-value组成,每个key-value对表示一个字段,value可以是基础类型,也可以是一个子消息。
其中,key表示了该字段数据类型,字段id信息,而value则是该字段的原始数据。若字段类型是string/bytes/子message(长度不固定),则在key和value之间还有一个值表示该字段的数据长度,如下图所示:
key值的计算方式为:key=(id<<3)|type,其中id是在消息定义时的字段id,而type表示数据类型,取值范围0-5,如下表所示:
例:假如proto文件定义如下,其中消息取值为code=10,msg="abc",尝试计算序列化后的消息数据。
message Response {
required int32 code = 1;
required string msg = 2;
}
答:
a. 消息的最终结构为key1-value1-key2-length2-value2;
b. 其中,key1=(id<<3)|type = (1<<3)|0=0x08,value1=0x0a,key2=(id<<3)|type=(2<<3)|2=0x12,length2=0x03,value2=0x616263;
c. 因此,最终的消息数据为:0x080a1203616263.
在java中,int用4字节表示,long用8字节表示,而在实际场景中,一般我们不会使用到很大的数,因此使用4/8字节表示数值会有冗余。为了进一步减小序列化后的数据大小,protobuf引入了varint编码,解决数值表示过程中的冗余问题。
在protobuf中,key值/length/int32/int64/uint32/uint64使用了varint编码表示。
varint编码原理如下图所示,每个字节首位表示后面是否还有字节,而后7位表示数据。图中所示的数据,用二进制表示实际上是00101000100111(protobuf采用小端模式),即2599。
由于负数在计算机中用补码表示,首位永远是1,无法使用varint编码进行压缩。为了解决此问题,protobuf引入了zigzag编码,目前,sint32和sint64类型的数据使用zigzag编码。
zigzag编码原理是,首先按照下表中的规律,将数据全部转化成正数,然后再用varint进行编码。
例:-3(int类型)用zigzag编码处理后,结果为0x05,仅需1个字节;而若不用zigzag,在内存中表示为0xFFFFFFFD,需要4个字节;若直接使用varint编码,则是0xFEFFFFFF0F,需要5个字节。