Protobuf为什么快
Protobuf接收/发送方都需要维护一个IDL,其中格式如下
例:Protobuf的IDL
message Person {
required int32 id = 1;
required string name = 2;
required string hobby = 3;
required string introduction = 4;
}
注意:Protobuf在传输时,只需要传输value,因此速度就可以做到比其他序列化更快了
如下面这段Protobuf字节流(|为了做区分打上的, 实际上是另一种表现形式)
1|zmp|play tennis|hello world
接收端接收到之后,就可以按照事先定义好的IDL解析成key-value
id=1
name=zmp
hobby=play tennis
introduction=hello world
问题1:如何划分数据
在Protobuf中, 会生成一个Tag字段,用于区分数据
传送格式:
Tag
1Tag
zmpTag
play tennisTag
hello world
其中的Tag生成:
static int makeTag(final int fieldNumber, final int wireType) {
return (filedNumber << 3) | wireType;
}
其中fieldNumber
代表的是IDL中的编号, 例如上面id的编号就为1
wireType
的数字可以根据下表所示
Type | Meaning | Used For |
---|---|---|
0 | Varint | int32, int64, uint32, uint64, sint32, sint64, bool, enum |
1 | 64bit | fixed64, sfixed64, double |
2 | Length-delimited | string, bytes, embedded messages, packed repeated fields |
3 | Start group | groups(deprecated) |
4 | End group | groups(deprecated) |
5 | 32bit | fixed32, sfixed32, float |
可以看到wireType
仅需要3位即可完成(从000 ~ 101)
那么就知道了生成的Tag中, 低3位是wireType
,然后后面的就是自己定义的fieldNumber
了
问题2:如果传输过程中发现了和Tag格式一致的字节怎么办
因为Tag生成后是一个1字节的int类型,那么不可避免我们的业务数据中有一个字节会巧合与Tag的格式一样
传输数字时
而Protobuf在传输信息时,对于数字都会使用varint编码和zigzag编码,关于这两种编码方式这里不展开了,大家可以去搜索一下有很多相关原理介绍
例如-1的编码过程如下所示
-1的补码表示形式为
11111111 11111111 11111111 11111111
进行zigzag之后
00000000 00000000 00000000 00000001
再进行varint编码
00000001
可见省去了3/4的体积
例如一开始要传输一个数字,我们拿到第一个Tag,解析出它的fieldNumber
和wireType
,在varint
编码的作用下,高位为1表示下一个字节还是该数字,如果为0则表示下一个字节是Tag了。
传输字符串时
对于传输字符串的情况,使用varint编码的效率就比较低了,这时候可以添加一个长度相关信息,使其整个格式为Tag-Length-Value
形式,Tag为分隔符,那么Length同样也可以使用Varint编码方式
如果一开始传输的是一个字符串,拿到Tag后,就知道接下来是一个字符串,那么下一个字节就开始解析Length,Length同样适用varint
编码,遇到0后辨识该Length解析完成,那么就知道接下来Value的长度,按照长度取完字符串之后,那么下一个就又回到了Tag
以此类推,Protobuf永远知道哪一个字节是Tag,这样就可以很好的进行区分