序列化protobuf

来源:互联网 发布:淘宝网卫裤 编辑:程序博客网 时间:2024/06/02 03:02

参考:

https://developers.google.com/protocol-buffers/

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

属性标记

protobuf的属性的三个标识
required,序列化的内容里必须有这个属性键对key-value pair
optional,序列化的内容里有0个或1个属性键对
repeated,序列化的内容里有0个或多个属性键对

当保存Varint类型时,每个字节的最高位(1:有;0:无)表示后面是否还有字节传过来,低7位存储数值的补码,
一般来说在计算机保存int都是4个byte,当protobuf序列化int的话,数值越小,则占用的字节越小,那么序列化后的二进制也就是越小,
除非是非常大的数在Varint中会占5个字节,
一般来说,除非是非常大的数,否则protobuf序列化int后占用的字节比一般int占用的字节少。

序列化
getSerializedSize()计算对象的数据大小,

如果接收端无法识别field key,则会跳过该key;
每个key会包含两部分
1.proto文件定义field key的序列field_number
2.key的wire_type
根据这两部分,可以知道field key对应的value值长度

每个key由以下计算得来(结果为varint类型):
(field_number << 3) | wire_type
则最后3位保存着wire_type的值

比如key序列化为0X08则
0000 1000
去掉最高标识位,剩下
_000 1000
最后3位为000,即是0,看下面的wire_type列表,知道是Varint,然后右移3位000 0001得到field_number为1,知道key在proto文件定义的序号是1

wire type 列表
------------------------------------------------------------------------------------
Type    Meaning             Used For
------------------------------------------------------------------------------------
0       Varint              int32, int64, uint32, uint64, sint32, sint64, bool, enum
1       64-bit              fixed64, sfixed64, double
2       Length-delimited    string, bytes, embedded messages, packed repeated fields
3       Start group         groups (deprecated)
4       End group           groups (deprecated)
5       32-bit              fixed32, sfixed32, float
------------------------------------------------------------------------------------

------------------------------------------------------------------------------------

wire_type:1

------------------------------------------------------------------------------------
比如300,protobuf序列化:
1010 1100 0000 0010
每个byte去年最高位标识位,则每组byte各剩下低7位
_010 1100 _000 0010
protobuf采用小端字节保存值,则反转两组7位
000 0010 和 010 1100 拼接起来:100101100 = 300

int:
max_value:01111111111111111111111111111111(正数)
min_value:10000000000000000000000000000000(负数)

负数的负号一般计算机是采用最高位来表示的,所以负数被表示为很大的数,如果使用Varint的int32或者int64来表示负数,则需要5个byte来个表示。protobuf采用了ZigZag来编码有符号数

ZigZag

==============================
Signed Original | Encoded As
------------------------------
0                  0
------------------------------
-1                 1
------------------------------
1                  2
------------------------------
-2                 3
------------------------------
2147483647         4294967294
------------------------------
-2147483648        4294967295
==============================

In other words, each value n is encoded using

(n << 1) ^ (n >> 31)

for sint32s, or

(n << 1) ^ (n >> 63)

for the 64-bit version.

所以有符号的整数应该用Varint中的sint32或者sint64

如:
message Hello{
    required sint32 kk = 1;
    required sint64 bb = 2;
}
kk,bb使用sint32,sint64,protobuf序列化时会使用ZigZag编码。
假设Hello的属性kk设值-1;属性bb设值-2;protobug序列化后的二进制只有4个字节0X08 01 10 03
序号:字节值(进制)
------------------------
0:8(16)========1000(2)
1:1(16)========1(2)
2:10(16)========10000(2)
3:3(16)========11(2)

message Hello{
    required int32 kk = 1;
    required int64 bb = 2;
}
如果使用int32,int64,则序列化后的字节数为22,是原来的5倍多,汗!
=============================length:22
0:8(16)========1000(2)
1:-1(16)========11111111111111111111111111111111(2)
2:-1(16)========11111111111111111111111111111111(2)
3:-1(16)========11111111111111111111111111111111(2)
4:-1(16)========11111111111111111111111111111111(2)
5:-1(16)========11111111111111111111111111111111(2)
6:-1(16)========11111111111111111111111111111111(2)
7:-1(16)========11111111111111111111111111111111(2)
8:-1(16)========11111111111111111111111111111111(2)
9:-1(16)========11111111111111111111111111111111(2)
10:1(16)========1(2)
11:10(16)========10000(2)
12:-2(16)========11111111111111111111111111111110(2)
13:-1(16)========11111111111111111111111111111111(2)
14:-1(16)========11111111111111111111111111111111(2)
15:-1(16)========11111111111111111111111111111111(2)
16:-1(16)========11111111111111111111111111111111(2)
17:-1(16)========11111111111111111111111111111111(2)
18:-1(16)========11111111111111111111111111111111(2)
19:-1(16)========11111111111111111111111111111111(2)
20:-1(16)========11111111111111111111111111111111(2)
21:1(16)========1(2)

------------------------------------------------------------------------------------

wire_type:2

------------------------------------------------------------------------------------
对于Type:2的wire type,包括string,和embedded messages(即我们自定义的类对应的类型),和packed repeated fields类型,序列化后的二进制跟Varint不同的是,
不仅仅是key-value形式,Type为2的wire type还会指明value的字节长度,如
message Test2 {
  required string b = 2;
}
序列化是
12 07 74 65 73 74 69 6e 67
其中
0X12,二进制0001 0010,wire type是2,field number是2
0X07,表明value占用7个字节
0X74 65 73 74 69 6e 67,value值testing

message Test1 {
  required int32 a = 1;
}
a传值150
key a序列化为
08 96 01

message Test3 {
  required Test1 c = 3;
}
将Test1 c的a设为150,
则序列化为
1a 03 08 96 01
其中
0X1a,0001 1010,field_number:3,wire_type:2,自定义的类,wire type也是2,跟string一样
0X03,value值长度3
0X08 96 01,值为150

------------------------------------------------------------------------------------

Optional And Repeated Elements

------------------------------------------------------------------------------------
The order of the elements with respect to each other is preserved when parsing, though the ordering with respect to other fields is lost.
在protobuf verson2.0,repeated的[packed=true]是可选的;到了versio3.0,repeated的[packed=true]是默认的。
不过,只有当repeated标识的属性wire type是Varint(wire type:0), 32-bit(wire type:5), 或者 64-bit(wire type:1)才可以声明[packed=true]

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

假设Test4对象的属性field d有值3,270和86942,则采用packed序列化成二进制:

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)

------------------------------------------------------------------------------------

Maps

------------------------------------------------------------------------------------
map<key_type, value_type> map_field = N;
map的key只能是整型 或者 string type,value除了不能是map类型,其他类型都可以。

其实,protobuf中的map是等同于下面这种message的:
message MapFieldEntry {
  optional key_type key = 1;
  optional value_type value = 2;
}

repeated MapFieldEntry map_field = N;

protoc -I=. --java_out=D:\project\src\main\java Foo.proto

Foo.proto文件是在.目录下的,即是在当前目录下的。
--java_out就生成java文件后放在的目录

注意:
compile编译器的版本,和protobuf-java的版本要保证兼容。
java_outer_classname

比如proto文件:
syntax = "proto3";

package lam.serialization.protobuf;
option java_package = "lam.serialization.protobuf";
option java_outer_classname = "FooProto";

message Foo{
    map<int32, string> map_field = 1;
}

注意,java_outer_classname的值不能和Foo同名,否则编译器编译时出异常
其中,将Foo的map属性map_field设置为{"1":"ONE","2":"SECOND","0":"ZERO"}
序列化二进制为:
length of byte[]:31
index:16进制数
0:A===== 00001010:低3位010:2,表示wire type是2;右移3位得到1,表示field number是1.

1:7===== 00000111
2:8===== 00001000:map中的key,wire type是0,field number是1
3:1===== key的值是1
4:12===== 00010010:map中的value,wire type是2(Length-delimited,则紧接长度,然后再是值),field number是2,其中protobuf中的map等同于自定义一个message MapFieldEntry
5:3===== value值的长度3
6:4F=====O
7:4E=====N
8:45=====E
9:A=====

10:A=====

11:8=====
12:2=====
13:12=====
14:6=====
15:53=====S
16:45=====E
17:43=====C
18:4F=====O
19:4E=====N
20:44=====D
21:A=====

22:8=====
23:8=====
24:0=====
25:12=====
26:4=====
27:5A=====Z
28:45=====E
29:52=====R
30:4F=====O

可见map中的key-value对,其中也是可以当作多个message来序列化成二进制的。
原创粉丝点击