当前位置 : 首页 » 文章分类 :  开发  »  Protobuf

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

阅读
评论
1.3k
阅读预计4分钟
创建日期 2021-03-16
修改日期 2022-12-13
类别
标签

页面信息

location:
protocol:
host:
hostname:
origin:
pathname:
href:
document:
referrer:
navigator:
platform:
userAgent:

评论