|
前言:
引用自同事的一次技术分享,内容简单易懂,例子生动,不信你们看不懂。
1. message布局
message TestA {
uint64 aa = 1;
}以上面message为例子,message序列化的内容为
- 字段信息(tag)
- field_num:字段的序列号,也就是【uint64 aa =1;】中的这个1
- writetype:字段类型,也就是【uint64 aa =1;】中的uint64
- 值(value)
字段信息和对应值组成键值对(key-value),key也叫tag,tag是按varint编码,value是根据tag里面的writetype来决定用什么方式编码;
字段
下图为类型为0(蓝色),序列号为1(绿色)的字段系信息
- 标志位:第1位为标志位(msb),0表示后续没有字节,1表示后续还有字节要读;
- 序列号编码:第2-5位为序列编码(field_num),序列号的长度是可变的,取决于msb,上图中序列号0001表示1,也就是field_num=1,当field_num超过16的时候,序列号编码就会按字节增长,例如field_num=16时,整个字段的编码就变为1000 0001 0000 0000,加黑的部分提取出来00000010000=16
- 类型编码:第6-8位为类型编码,writetype, 不管字段信息占多少个字节长度,最后3位都表示字段类。writetype
- 0: varint, 表示后面的value采用base 128varint读取,表示类型包括int32、int64、uin32、uint64、sint32、sint64、bool、enum
- 1: 64_bit, 表示固定读取64bit,表示类型包括fixed64、sfixed64、double
- 2: length-delimited,后面有一个length,表示接下来要读取length个字节;表示类型包括string、bytes、嵌套类型、repeated字段
- 3-4:作废;
- 5:32_bit, 表示固定读取3bit,表示类型包括fixed32、sfixed32、float
ps:只有类型2是 tag+length+value三个值,其他类型都是tag+value;
如果message里面嵌套message,其实本质上也是一段tag-value格式的二进制数据;
如果是repeated模式,在 proto2 中默认是没有加上 [packed=true] ,所以是tag-value ... tag-value的格式; proto3版本默认是加上的,就是是tag-value-value-...-value模式,省去了后面的tag;
值
下图值为1(绿色)
- 标志位:第1位为标志位(msb),0表示后续没有字节,1表示后续还有字节要。与字段信息的msb一个含义
- 字段值编码:第2-8位为值编码,上图0000001表示当前值为1,如果字段值大小超过了2的7次方,例如值=2的7次方,那么整个值信息变为10000 0001 0000 0000,把加黑的部分提出出来000000010000000=2的7次方
2. Signed Integers 编码
varint的最高位一般用来表示是否继续读取字节,那有符号数怎么处理?
Protobuf 的内部将 int32 类型的负数转换为 uint64 来处理,转换后的 uint64 数值的高位全为 1, 相当于是一个 8 字节的很大的无符号数,
而int64由于是有符号数的原因,因此最高位的1也不能丢弃,
因此采用 Base128 Varints 编码后将恒定占用 10 个字节的空间,因为 ceil(64/7) = 10;
protobuf定义了sint32,sint64这两种类型,采用zigzag编码;
zigzag映射函数为:
Zigzag(n) = (n << 1) ^ (n >> 31), n 为 sint32 时
Zigzag(n) = (n << 1) ^ (n >> 63), n 为 sint64 时
按照这种方法,-1 将会被编码成 1,1 将会被编码成 2,-2 会被编码成 3,如下表所示:
说白了,就是按规则把负数映射成正数;
3. 例子详解
message TestA {
uint64 aa = 1;
}
message TestPb {
uint32 a = 1;
double b = 2;
float c = 3;
repeated uint64 d = 4;
string e = 16;
TestA f = 5;
int32 g = 6;
sint32 h = 7;
}赋值:
ta := &TestPb{
A: 1,
B: 1.1,
C: 1.1,
D: []uint64{256, 1, 2},
E: &#34;abc&#34;,
F: &testpb.TestA{
Aa: 2,
},
G: -1,
H: -1,
}
taa, _ := proto.Marshal(ta)
fmt.Println(&#34;marshal TestPb:&#34;, taa)
打印Marshal后的二进制:
[8 1 17 154 153 153 153 153 153 241 63 29 205 204 140 63 34 4 128 2 1 2 42 2 8 2 48 255 255 255 255 255 255 255 255 255 1 56 1 130 1 3 97 98 99]
解析:
- uint32 a = 1,赋值1
8: 0 0001 000, msb 0 没有后续字节, 0001对应field_num=1, 000对应writetype为0,采用varint;
1: 0 0000001, msb 0, 0000001对应value为1;
- double b = 2,赋值1.1
17: 0 0010 001, msb 0, 0010对应field_num=2, 001对应writetype为1,后续读取64_bit;
154 153 153 153 153 153 241 63:对应double 1.1在内存的二进制;
- float c = 3,赋值1.1
29: 0 0011 101, msb 0, 0011对应field_nun=3, 101对应writetype为5,后续读取32_bit;
205 204 140 63:对应float1.1在内存的二进制;
- repeated uint64 d = 4,赋值256, 1, 2
34: 0 0100 010,msb0, 0100对应field_num = 4, 010对应writetype为2,需要读取一个length长度的字节;
4: length,后续读取4字节;
128 2:1000 0000,0000 0010,去掉msb后转化: 000 0010, 000 0000(小端模式,需要逆序) 等于256
1 2:0000 0001, 0000 0010 对应1, 2
- TestA f = 5,赋值&testpb.TestA{Aa: 2}
42:0 0101 010,msb 0,0101对应field_num = 5, 010对应writetype为2,需要读取一个length长度字节;
2:length,后续读取2个字节;
8:0 0001 000,msb0, 0001对应field_nun =1, 000对应writetype为0,采用varint;
2:0000 0010对应2;
- int32 g = 6,赋值-1
48:0011 0000,msb 0, 0110对应field_num=6, 000对应writetype为0,采用vatint;
255 255 255 255 255 255 255 255 255 1 (注:Protobuf 的内部将 int32 类型的负数转换为 uint64 来处理,所以相当于是8字节) 解码后对应为-1,详情参考上面2.2节;
- sint32 h=7, 赋值-1
56: 0011 1000,msb 0, 0111对应field_num=7, 000对应writetype为0,采用vatint;
1:因为这里是sint32类型,所以需要用zigzag解码,1解码后为-1;
- string e = 16,赋值“abc”
130 1: 1000 0010, 0000 0001, msb为1,所以需要读取后续一个字节,去掉msb逆序后为:000 0001 000 0010,1 000 0对应field_num=16, 010对应writetype为2,需要读取一个length长度的字节;
3: length,后续读取3个字节;
97 98 99: 对应字符 “abc”
|
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|