Protobuf
Protobuf 使用笔记
Protocol Buffers 官方页面
https://developers.google.com/protocol-buffers
protocolbuffers / protobuf
https://github.com/protocolbuffers/protobuf
Protobuf 是 google 提出的一种独立于语言的二进制数据交换格式,效率和兼容性都很优秀,很适合做数据存储或 RPC 数据交换格式
required:必须初始化字段,如果没有赋值,在数据序列化时会抛出异常
optional:可选字段,可以不必初始化。
repeated:数据可以重复(相当于java 中的Array或List)
编码规则
一个 protocol buffer 是一系列键值对
1)在消息流中每个Tag(key/键)都是varint,编码方式为:field_num << 3 | wire_type。即,Tag(key/键)由 .proto 文件中字段的编号(field_num) 和 传输类型(wire_type)两部分组成。
注:Tag也是Varints编码,其后三位是传输类型(wire_type),之前的数值为是字段编号(field_num)。
注意并不是说Tag只能是一个字节,这里说了Tag也是用Varint编码,显然使用Varint编码方式几千/几万的字段序号(field_num)都是可以被表示的。
2)在对一条消息(message)进行编码的时候是把该消息中所有的key-value对序列化成二进制字节流;key和value分别采用不同的编码方式。
3)消息的二进制格式只使用消息字段的字段编号(field_num)作为Tag(key/键)的一部分,字段名和声名类型只能在解析端通过引用参考消息类型的定义(即 .proto 文件)才能确定。
4)解码的时候解码程序(解码器)读入二进制的字节流,解析出每一个key-value对;如果解码过程中遇到识别不出来的filed_num就直接跳过。这样的机制保证了即使该消息(message)添加了新的字段,也不会影响旧的编/解码程序正常工作。
key 的最低 3 个 bit 为 wire type
Type | Meaning | Used For |
---|---|---|
0 | Varint | int32, int64, uint32, uint64, sint32, sint64, bool, enum |
1 | 64-bit | fixed64, sfixed64, double |
2 | Length-delimi | 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=0、1、5,编码为key+数据,只有一个数据,可能占数个字节,数据在编码时自带终止标记;
wire type=2,编码为key+length+数据,length指示了数据长度,可能有多个数据,顺序排序。
如果出现嵌套message,直接将嵌套message部分的编码接在length后即可;
repeated后面接的字段,如果是个message,它重复出现多少次,编码时其key就会出现几次;如果接的是proto定义的字段,且以packed = true压缩存储时,只会出现1个key;如果不以压缩方式存储,其key也会出现多次。在proto3中,默认以压缩方式进行存储,proto2中则需要显式地声明。
varint变长整数表示
一个整数一般是以32位来表示的,存储需要4个字节。
当如果整数大小在256以内,那么只需要用一个字节就可以存储这个整数,这样剩下的3个字节的存储空间空闲。
以此类推,只有整数大于 16777216 时才需要4字节来存储。
varint 是一种使用变长方式表示整数的方法,可以使用一个或者多个字节来表示小整数和大整数,数越小,使用的字节数越少。
在varint表示的字节中,除了最后一个字节,前面的字节都有一个bit来表示还有字节需要处理,这个标记叫做most significant bit (msb) set。低位放在前面。
所以 varint 的每个字节只有7位可以用来存储真正的数据。所以,Varint将数按照7位分段后存储。
Varint 中的每个字节的最高位 bit 有特殊的含义,如果该位为 1,表示后续的 byte 也是该数字的一部分,如果该位为 0,则结束。
此外,varint采用小端序的方式编码(little-endian byte order),低字节在前。
1表示为 0000 0001
最高位0表示这是最后一个字节了,只用一个字节就可以表示。
数字300的 varint 表示为 1010 1100, 0000 0010
, 两个字节来表示,第一个字节最高位是1表示后一个字节也是数字的一部分,第二个字节最高位是0表示结束。
转换为普通二进制表示的过程如下:
将每个字节高位去掉得 010 1100, 000 0010
由于是低字节在前,将两个7位反过来,得 000 0010, 010 1100
去掉前面的0得 1 0010 1100
, 也就是 300 的二进制表示。
缺点是,大的数字则需要 5 个 byte 来表示,但多数情况下使用varint更节省字节数。
Zigzag算法处理负数
Zigzag算法用于将负数转换为正数,然后再使用 varint 表示。
正数:当前的数乘以2, zigzagY = x * 2
负数:当前的数乘以-2后减1, zigzagY = x * -2 - 1
Thrift中对数值的发送做法是:先做zigzag得到一个数,再做varint数值压缩。
在Java中使用Protobuf
Protocol Buffer Basics: Java
https://developers.google.com/protocol-buffers/docs/javatutorial
上一篇 Homebrew
下一篇 Snack3
页面信息
location:
protocol
: host
: hostname
: origin
: pathname
: href
: document:
referrer
: navigator:
platform
: userAgent
: