ProtoBuf解析介绍

Protobuf是Google开源的一款类似于Json,XML数据交换格式,其内部数据是纯二进制格式,不依赖于语言和平台,具有简单,数据量小,快速等优点。目前用于序列化与反序列化官方支持的语言有C++,C#, GO, JAVA, PYTHON。适用于大小在1M以内的数据,因为像在移动设备平台,内存是很珍贵。

使用方法也比较简单:

  • 定义用于消息文件.proto
  • 使用protobuf的编译器编译消息文件
  • 使用编译好对应语言的类文件进行消息的序列化与反序列化

先来定义一个简单的消息:

message Person {
   int32 id = 1;//24
   string name = 2;//wujingchao
   string email = 3;//wujingchao92@gmail.com
}

实际的二进制消息为:

08 18 12 0a 77 75 6a 69 6e 67 63 68 61 6f 1a 16 77 75 6a 69 6e 67 63 68 61 6f 39 32 40 67 6d 61 69 6c 2e 63 6f 6d

下面就讲解这段二进制流数据是怎么组成的:

Varints

一般情况下int类型都是固定4个字节,protobuf定义了一种变长的int,每个字节最高位表示后面还有没有字节,低7位就为实际的值,并且使用小端的表示方法。例如1,varint的表示方法就为:

0000 0001

是不是这样就省了三个字节。

再例如300,4字节表示为:10 0101100,varint表示为:

10101100 00000010

所以前面消息为Person的id的值为00011000,即0x18。

负数的最高位为1,如果负数也使用这种方式表示就会出现一个问题,int32总是需要5个字节,int64总是需要10个字节。

所以定义了另外一种类型:sint32,sint64。采用ZigZag编码,所有的负数都使用正数表示,计算方式:

  • sint32
    (n << 1) ^ (n >> 31)
  • sint64
    (n << 1) ^ (n >> 63)
Signed OriginalEncoded As
00
-11
12
-23
21474836474294967294
-21474836484294967295

使用Varint编码的类型有int32, int64, uint32, uint64, sint32, sint64, bool, enum。Java里面没有对应的无符号类型,int32与uint32一样。

Wire Type

每个消息项前面都会有对应的tag,才能解析对应的数据类型,表示tag的数据类型也是Varint。

tag的计算方式: (field_number << 3) | wire_type

每种数据类型都有对应的wire_type:

Wire TypeMeaning Used For
0Varint int32, int64, uint32, uint64, sint32, sint64, bool, enum
164-bit fixed64, sfixed64, double
2Length-delimited string, bytes, embedded messages, packed repeated fields
3Start group groups (deprecated)
4End group groups (deprecated)
532-bit fixed32, sfixed32, float

所以wire_type最多只能支持8种,目前有6种。

所以前面Person的id,field_number为1,wire_type为0,所以对应的tag为

1 <<< 3 | 0  = 0x08

Person的name,field_number为2,wire_type为2,所以对应的tag为

2 <<< 3 | 2 = 0x12

对应Length-delimited的wire type,后面紧跟着的Varint类型表示数据的字节数。

所以name的tag后面紧跟的0x0a表示后面的数据长度为10个字节,即"wujingchao"的UTF-8 编码或者ASCII值:

08 18 12 0a 77 75 6a 69 6e 67 63 68 61 6f 1a 16

嵌套的消息类型embedded messages与packed repeated fields也是使用这种方式表示,对应默认值的数据,是不会写进protobuf消息里面的。

packed repeated与repeated的区别在于编码方式不一样,repeated将多个属性类型与值分开存储。而packed repeated采用Length-delimited方式。下面这个是官方文档的例子:

message Test4 {
    repeated int32 d = 4 [packed=true];
}

22        // tag (field number 4, wire type 2)
06        // payload size (6 bytes)
03        // first element (varint 3)
8E 02     // second element (varint 270)
9E A7 05  // third element (varint 86942)

如果没有packed的属性是这样存储的:

20 //tag(field number 4,wire type 0)
03 //first element (varint 3)
20 //tag(field number 4,wire type 0)
8E 02//second element (varint 270)
20 //tag(field number 4,wire type 0)
9E A7 05  // third element (varint 86942)

是不是这种方式比较节省内存,所以proto3的repeated默认就是使用packed这种方式来存储。(proto2与proto3区别在于.proto的语法)。

有了以上的相关概念,我们在读protobuf的源码就比较容易了。

参考:https://developers.google.com/protocol-buffers/docs/encoding



文/吴景超(简书作者)
原文链接:http://www.jianshu.com/p/e0d81a9963e9
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值