最近看了下protobuf,研究的版本还是比较老的2.6.1,最主要的原因是对protobuf能否压缩传输数据的原理感兴趣。写个博客记录一下,免得以后忘了。
先附上github的demo地址:https://github.com/aa5279aa/protobuff_androiddemo
demo当中包含
1、客户端工程ProtobufClient,客户端通过注解进行反序列化
2、服务端工程ProtobufWeb,单纯的使用原生的protobuf生成的文件,依赖的jar包在lib文件夹当中。
3、contract工程ProtobufContract,定义契约的工程。当前工程用于生成google原生的.java契约文件。
4、单独的javaSE工程ProtobufForEclipse,里面包含Test测试类,提供序列化和反序列化测试。
一、protobuf的定义模型结构
首先,先看一下protobuf的数据定义结构,下面是一个例子。
option java_package = "com.lxl.servlet.pbmodel"; option java_outer_classname = "DemoRequestProto"; message DemoRequest{ optional int32 valueInt32 = 1; //请求参数1 optional int64 valueInt64 = 2; //请求参数2 optional string valueString = 3; //请求参数3 }
二、protobuf的数据结构:
2.1、解析tag
第一位标记是否拓展下一字节,1代表拓展,0代表不拓展。
tag:默认情况下,tag占据4位,最多标识15以下,如果超过15,则拓展到下一个字节。
type:共有6种类型,用3位来标识足够了。
举个例子,上文中的中,tag是1,类型是0(WIRETYPE_VARINT)。
首先长度足够没有拓展,那么第一位就是0
其次tag的值是1,那么2到5位就是00001
最后type的类型是0,那么最后6到8位就是000
最后合起来就是0 0001 000 = 8(十进制)
2.2、解析value,int类型
value的字节当中,第一位标识是否需要拓展到下一个字节,1代表需要,0代表不需要。
如果标识的数据值小于2的7次方(小于128)时,则可以用一个字节标识,否则需要多个字节。
同样举个例子,上面demo中的
如果valueInt32=5时,则value中8位的标识值为 0 0000101
如果valueInt32=1000时(大于128),则value包含两个字节,第一个字节标识int中的1到7位,则是:1 1101000 (-24)
第二个字节标识int中的8到14位,则是:0 0000111(7)
说到这里,拓展一下,如果用json标识的话:
"valueInt32":1000
长度为17,则需要17个字节来标识。
比例就是3:17。protobuf的优势就体现出来了。
2.3、解析value,string类型
value当中标识string类型,则第一个字节标识的是字符串长度,同样的也是用7位来标识,不够的话拓展到下一个字节。
后面的字节标识就是具体的string的值了。
举个例子,
optional string valueString = 3; //请求参数3
valueString ="suc哈"
长度则为3+3=6(protobuf中使用的UTF-8的编码格式,一个中文占3个字节)。对应的value就是
6,115,117,99,-27,-109,-120
6代表长度,115代表s,117代表u,99代表c,-27,-109,-120代表“哈”
tag=3,则3<<<3=24,加上type=2,则为26。
所以加上tag,就是:26,6,115,117,99,-27,-109,-120
2.4、解析value,model类型
后续待补充...
感想总结
总结一下,protobuf之所以相对于json能否节省传输数据,原因有如下几点:
1、针对int类型,采取了变长的传输方式。json传输int都是
2、不传输属性字段名(属性名长度多少就是几个字节),取而代替的是int类型的tag值(基本都是1个字节)。
3、不传输多余的字符,比如json中分割数据的{,"等等。
值得学习的几点:
1、针对大概率的场景去做优化,而不用太在乎极限的场景。比如protobuf中极限情况下int会占据5个字节,要比json还要多。但是大概率的情况下,都是要比json节省的。
2、打破int类型就是4个字节的常规,使用变长的策略。拓展一下,是否有更节省的策略呢?