转载请注明出处 http://blog.csdn.net/manchew/article/details/39523197
上节讲了什么是Protocol Buffers,以及如何使用。 我们知道Protocol Buffers有着高性能,低空间占用,那么,它到底是如何做到的呢?本节就深入了解一下它的实现机制。
其实任何序列化,反序列化的东东,不过就是它们的编解码过程,那么我们看看Protocol Buffers如何在Google大牛的思想下妙笔开花。
在介绍之前,先来普及几个概念:
protocol buffers里面的字段最终编码后序列化为以key-value的形式被读取或保存。
Wire Type 用来标识我们以什么方式去解析字段的方法,主要的Wire Type有以下六种,到底Wire Type的中文名叫什么,确实也不太好翻译,哪位大婶知道好的名字,不妨说来听听。
可以看到Wire type会包含很多种字段类型,换句话讲就是,int32, int 64这些都用Varint的方式去解析,而fixed64这些都会用64-bit的这种方式去解析,说这些比较抽象,之后我会用例子来说明一下。
Key用来标识后面的数据是哪个字段的值,Key被定义为( tag << 3 ) | wire type, 这样的话key的最后面3bit就是wire type, key的值右移3bit后就得到tag的值
64-bit编解码
如果定义一个message test,代码如下:
message Test
{
required fixed64 id = 1;
}
接下来调用
...
int main(){
Test msg,
msg.set_id(5);
}
...
内存模型应该是这个样子的
tag为00001,对应id这个字段,wire type为001,后面的数据解析方式为64-bit的形式,64-bit的意思是,64-bit为value的值,所以id的值为5,注意Google Protocol Buffers是按小端存取数据的。虽然后面56bit都为0,但这里还是要占用7个字节。这样显得有点儿浪费空间,前面也有讲过,protocol buffers最显著的特点就是解析快,省空间。这时,wire type为 varint的方式就派上用场了。
Varint,从名字来看,就是可变的int, 我们知道,一个int在32位机器上占4个字节,如果用varint的话就不定占4个字节了,尤其你存储的是小数据时,根本不需要4个字节来存储。
Varint把第一个字节的第一个bit赋予新的意义,如果为1表示当前的值还没有表示完,后面还有字节应该属于当前值,直到遇到首位为0的字节,才表示后面没有更多的字节了。
第一步,先转化为二进制格式,然后从后面取7bit放在低地址位,因为后面还有字节来表示当前值,所以首位给1
第二步,再从二进制格式中,取7bit放在高地址位,因为这个字节已经是最后一个字节,再没有其它的字节了,所以首位给0
这样就完成了varint类型的编码了。
了解Varint的编码过程,解码就比较容易了。
例如有message
message Test{
required int32 a = 1;
}
另外,Protocol buffers还提供了Length-Delimited方式的wire type来编解码,这种类型主要用于value为可变长度类型时使用,比如string就是一个典型的例子。
这种方式在解码的过程中,同样先解码key,可以得知wire type,然后去取得value的值。
比如 Message
message Test {
required string b = 2;
}
编码后的内存模型为
我们对字段b进行赋值,下图显示了解码的全过程
对Key值进行解码,得到tag=2, wire type=2; 因为当前的wire type为 length-delimited类型,所以后面紧跟着length,指明value占有用多少个字节。这里注意的时,length也是varint类型,所以length本身所占字节并不是一个字节,而可以是更多个字节,如果这时字符串很长时。
关于Google Protocol Buffers的介绍就先到这里。