一味宠爱|Google Protocol Buffers 序列化原理( 二 )


ZigZag encoding
通过上面的示例 , 我们可以发现 , 负数由于最高位是符号位 , 肯定是 1 , 对于负数来说并不能起到压缩效果 , 针对这种情况 ProtoBuffer 提供了有符号整型(sint32 和 sint64) 。
这种类型怎么序列化呢?负数最高位符号位一定是 1 , 比如我们可以将符号位移到最低位 。 负数在计算机中是补码存储的 , 越接近 0 的负数 , 高位 1 越多 , 由于是补码取过反 , 我们是不是可以再取反下 , 将 1 转换成 0 。
下面来看 sint32 公式(64 位的将 31 换成 63)
(n << 1) ^ (n >> 31)

  • n<<1: 相当于将最低位空出来 。
  • n>>31: 带符号移位 , 正数就是 32 个 0 , 负数就是 32 个 1 。
  • 异或:相当于把符号位移到低位 , 并对负数做个取反 。
如果 128 以这种方式序列化后会是什么样?过程如下:
一味宠爱|Google Protocol Buffers 序列化原理与 0 异或相当于不做任何改变 , 所以对于正数来说 , 相当于做了左移一位 。 接下来看下 -1 转换情况:
一味宠爱|Google Protocol Buffers 序列化原理经过这样转换后对于数值比较小的负数 , 都转换成正数 , 来起到压缩的效果 。
03

字符串
定义如下结构:
message Test2 {optional string b = 1;}给 b 赋值 "java" 输出的字节数组如下:
[10, 4, 106, 97, 118, 97]第一个字节标记类型和序号 , 大家可以自己推算 。 第二个字节是长度 , 也是用压缩整形序列化 , 后面 4 个字节对应 “java” 这 4 个字符的 ASCII 码 。
04

嵌套类型
如下 Test3 中包含 Test1 数据
message Test3 {optional Test1 c = 1;}Test1 中的 a 赋值 128 , 将 Test3 序列化输出如下:
[10, 3, 8, -128, 1]
  • 10:0000 1010 对应的是 c 字符的位置和序号
  • 3:接下来内容大小 , 3 个字节
  • 8, -128, 1:对应的是 Test1 的数据
05

数组
repeated 是表示字段可以重复 。
message Test4 {repeated int32 d = 1;}这里给 d 添加 2 个值 “1” 和 “128” , 输出如下:
[8, 1, 8, -128, 1]
  • 8, 1:这里表示 1
  • 8, -128, 1:这里表示 128
这里跟基本类型序列化一样 , 只不过数组中每一个元素序列化的时候都有一个相同的 T (Type , 示例中的 8) 。
这里在序列化的时候 , ProtoBuffer 并不保证数组的元素一定是连续存储 , 有可能中间序列化的有别的元素 。
对于基本类型来说数组可以压缩 , 省略掉每个元素都要标记 T。 配置了压缩之后 , 数组就是连续存储 。
message Test4 {repeated int32 d = 1 [packed=true];}同样的 “1” 和 “128” 输出如下:
[10, 3, 1, -128, 1]
  • 10:field_number=1, wire_type=2 , 不定长 。
  • 3:长度 , 接下来 3 个字节存放数据 。
  • 1:0000 0001 , 高位是 0 说明是一个完整的数据 , 就是我们第一数据 1 。
  • -128 , 1:对应的是第二个数据 128
06

Map
Map 的序列化方式跟数组是差不多的 , 我们可以认为一个 key-value 是一个 item , 其实就相当于一个 item 数组 。
定义如下结构
message Test5 {map e = 1;}


推荐阅读