简介Protocolbuffer这么优秀的编码方式,底层是如何工作的呢?为什么能实现高效快速的数据传输?这一切都始于它的编码方式。定义一个简单的消息,我们知道protocolbuffer的主体就是消息。接下来,我们将从一条简单的消息入手,详细讲解protobuf中的编码方式。例如下面是一个非常简单的消息对象:messageStudent{optionalint32age=1;}在上面的例子中,我们定义了一个Student消息对象,并为其定义了一个名为age的字段,并设置了一个值,称为22.然后使用protobuf进行序列化。这么大的对象,序列化后的字节如下:089600很简单。三个字节可以表示一个messag对象,数据量很小。那么这三个字节是什么意思呢?一起来看看吧。Base128Varints在解释上面三个字节的含义之前,我们需要了解一下varints的概念。什么是Varints?即序列化整数时,占用的空间不同。小整数占空间小,大整数占空间大。这样就不需要固定一个具体的长度,可以减少数据的长度,但是会带来Parsing的复杂度。那么你怎么知道这个数据需要多少字节呢?在protobuf中,每个字节的最高位是判断位。如果该位设置为1,表示下一个字节和本字节在一起,表示同一个数。如果该位设置为0,则表示下一个字节与该字节无关,数据到该字节结束。例如,一个字节是8位。如果表示整数1,可以用下面的字节表示:00000001如果一个字节不能装整数,那么就需要用多个字节进行连接操作,比如下面表示的数据是300:1010110000000010为什么是300?先看第一个字节,它的第一位是1,说明后面还有一个字节。再看第二个字节,它的第一位是0,表示结束。我们把判断位去掉,改成下面的数:01011000000010这个时候就不能计算出数据的值了,因为在protobuf中,字节数是倒过来的,所以我们需要把上面两个字节交换一下Position:00000100101100即:100101100=256+32+8+4=300消息体的结构可以从message的定义中得知。protobuf中消息体的结构是key=value的形式,其中key是消息中定义的字段的整数值1、2、3、4等。而value就是实际给它设置的值。当一条消息被编码时,这些键和值将被连接起来形成一个字节流。解析的时候需要定位key和value的具体长度,所以key需要包含两部分,第一部分是proto文件中字段的值,第二部分是value部分占用的长度尺寸。只有结合这两部分的值,解析器才能正确解析字段。这种密钥格式称为有线类型。电线类型有哪些?看一下:类型含义使用场景0Varintint32,int64,uint32,uint64,sint32,sint64,bool,enum164-bitfixed64,sfixed64,double2Length-delimitedstring,bytes,embeddedmessages,packedrepeatedfields3Startgroup3groups(deprecated2groups(deprecated)4ps-bitfixed32,sfixed32,float可以看到除了3和4类型之外,其他类型可以分为三种,一种是定长类型,比如1、5,分别是64位和32位number,第二类是0,表示Varint,是一种变量类型,用来表示常见的数字类型,bool类型和枚举类型,第三类,2,表示长度区分的类型,通常用来表示字符串,字节数等所有的key都是varint类型,其值为:(field_number<<3)|wire_type,即key的后三位用来存放wiretype。我们上面例子中key的值为08,用二进制表示:0001000后三位为0,表示是Varint类型,将08右移三位,得到1,表示key代表的字段是1的字段,也是age。然后我们看剩下的部分9600,转换成二进制:9600=1001011000000000根据Varint的定义,第一位代表连接位,表示第二个字节的内容和第一个字的内容该部分是在一起的。对于Varint,需要交换低位字节和高位字节,如下:1001011000000000去掉最高位1:001011000000000交换低位字节和高位字节:000000000010110上面的值为16+4+2=22所以我们得到一个值为1的key,对应的值为22有符号整数我们知道有两种表示有符号整数的方式,一种是标准的int类型:int32和int64,另一种是signedint类型:sint32和sint64。两种类型的区别在于对应的负整数的表示方式。对于int32和int64,所有的负整数都是用十个字节来表示的,所以占用的空间会比较大,不适合表示负整数。如果使用sint32和sint64,则使用的编码是ZigZag,它对负整数更有效。ZigZag映射有符号整数和无符号整数。对于每个n,将使用以下公式进行编码:(n<<1)^(n>>31)对于sint64是:(n<<1)^(n>>64)例如:结果symbolicintegerencoding00-1112-2321474836474294967294-21474836484294967295#stringstring的wire类型为2,表示其值为varint编码的长度。例如:messageStudent{optionalstringname=2;}我们为Student定义第二个属性name,如果我们给name赋值“testing”,那么得到的code是:1207[74657374696e67]中括号的编码是“testing”的UTF8表示。0x12可以这样解析:0x12→00010010(二进制表示)→00010010(重组位)→field_number=2,wire_type=20x12表示字段2的类型为2,后面的07表示后面的长度字节。嵌套消息可以嵌套在消息中。我们来看一个例子:messageTeacher{optionalStudents=3;}如果我们把s的age字段设置为22,和第一个例子一样,那么上面的代码就是:1a03089600可以看到以下三个字节与第一个示例相同。前两个字节和字符串的判断方法是单值的,这里不再赘述。综上所述,protobuf的基本编码规则和实现已经说完了。听起来很棒?本文已收录于http://www.flydean.com/03-protobuf-encoding/最流行的解读,最深刻的干货,最简洁的教程,很多你不知道的小技巧等着你去探索!欢迎关注我的公众号:《程序那些事儿》,懂技术,更懂你!
