protobuf的实现原理

 

转载自:https://blog.csdn.net/aa5279aa/article/details/100187104

protobuf的实现原理

 

一、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位来标识足够了。

public static final int WIRETYPE_VARINT           = 0;//数字类型

  public static final int WIRETYPE_FIXED64          = 1;

  public static final int WIRETYPE_LENGTH_DELIMITED = 2;//变长类型,可以理解为大多数场景下都是string类型

  public static final int WIRETYPE_START_GROUP      = 3;

  public static final int WIRETYPE_END_GROUP        = 4;

  public static final int WIRETYPE_FIXED32          = 5;

 

举个例子,上文中的optional int32 valueInt32 = 1;    //请求参数1中,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中的

optional int32 valueInt32 = 1;    //请求参数1

如果valueInt32=5时,则value中8位的标识值为 0 0000101

则加上tag,完整的输出字节就是:00000100 00000101 (8,5)

如果valueInt32=1000时(大于128),则value包含两个字节,第一个字节标识int中的1到7位,则是:1 1101000 (-24)

第二个字节标识int中的8到14位,则是:0 0000111(7)

则加上tag完整的输出字节就是:00001000 11101000 00000111(8,-24,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,boolean类型

boolean类型在protobuf属于WIRETYPE_VARINT=0

value当中标识boolean类型时,使用0和1代替false和true。写法的方式和int类型一样。

所以如果一个字段,比如tag=1,定义为int类型,赋值为1的时候,序列化后为:8,1;

同样还是一个字段,比如tag=1,定义为boolean类型,赋值为true的时候,序列化后为:8,1。

遇到这种情况,即使序列化和反序列化定义不一致,也不会报错的。

2.5、解析value,model类型

model类型数据在protobuf属于WIRETYPE_LENGTH_DELIMITED=2。

这种类型结构和上面的string类型一样,属于tag + length + body的。

所以只需要把body中的字节,写入成model序列化之后的byte数组就可以了。

 

2.6、解析value,Array类型

针对array类型,我们还是举一个例子,上面的  

repeated int32 valueList = 5;    //请求参数List

我们可以看一下生成的protobuf源码:

for (int i = 0; i < valueList_.size(); i++) {

        output.writeInt32(5, valueList_.get(i));

      }

其实就是写入多个相同tag的数据。

比如给valueList添加值10,11,12,13。则加上tag=5,序列化之后的数据为:

40,10,40,11,40,12,40,13

 

 

感想总结

总结一下,protobuf之所以相对于json能否节省传输数据,原因有如下几点:

1、针对int类型,采取了变长的传输方式。json传输int都是

2、不传输属性字段名(属性名长度多少就是几个字节),取而代替的是int类型的tag值(基本都是1个字节)。

3、不传输多余的字符,比如json中分割数据的{,"等等。

值得学习的几点:

1、针对大概率的场景去做优化,而不用太在乎极限的场景。比如protobuf中极限情况下int会占据5个字节,要比json还要多。但是大概率的情况下,都是要比json节省的。

2、打破int类型就是4个字节的常规,使用变长的策略。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值