前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Google Protocol Buffers 序列化算法分析

Google Protocol Buffers 序列化算法分析

作者头像
Yano_nankai
发布2018-10-08 10:34:03
8250
发布2018-10-08 10:34:03
举报
文章被收录于专栏:二进制文集二进制文集

分析一下 Google Protocol Buffers 的序列化原理。介绍参考 Google Protocol Buffers 数据交换协议

说明

详细介绍参见官方文档

编写 proto 文件

定义了4个变量 a, b, c, d,其 Field 分别为 1~4。为了展示不同类型的序列化原理,将变量分别定义成 int32, int64, fixed64, string

代码语言:javascript
复制
option java_outer_classname = "YanoTestProbuf";

message YanoTest{
    optional int32 a = 1;
    optional int64 b = 2; 
    optional fixed64 c = 3; 
    optional string d = 4; 
}

测试字节码

代码语言:javascript
复制
@Test
public void testProtoBuf() {
    // encode bytes
    byte[] bytes = YanoTest.newBuilder().setA(1).setB(2).setC(3).setD("java").build().toByteArray();
    System.out.println(Arrays.toString(bytes));
    
    // decode bytes
    for (byte b : bytes) {
        System.out.println(getBinString(b));
    }
}

private String getBinString(byte b) {
    return String.format("%8s", Integer.toBinaryString(b)).replace(' ', '0');
}

生成的字节码

代码语言:javascript
复制
[8, 1, 16, 2, 25, 3, 0, 0, 0, 0, 0, 0, 0, 34, 4, 106, 97, 118, 97]

每个字节转换成二进制为:

代码语言:javascript
复制
00001000
00000001
00010000
00000010
00011001
00000011
00000000
00000000
00000000
00000000
00000000
00000000
00000000
00100010
00000100
01101010
01100001
01110110
01100001

让我们分析下这些字节码~~~_

分析字节码

首先要明确,Protocol Buffers 序列化的字节码是很紧凑的,而且是 key-value 的形式:

其中一个 key-value 就是一个 Field,就是定义的 abcd~

key 都是一个字节,以第一个 key 00001000 为例,key 有两个含义:

代码语言:javascript
复制
(field_number << 3) | wire_type

5 位 field_number + 3 位 wire_type,其中 wire_type 如下:

varint

Varint 是一种紧凑的表示数字的方法。它用一个或多个字节来表示一个数字,值越小的数字使用越少的字节数。这能减少用来表示数字的字节数。

比如对于 int32 类型的数字,一般需要 4 个 byte 来表示。但是采用 Varint,对于很小的 int32 类型的数字,则可以用 1 个 byte 来表示。当然凡事都有好的也有不好的一面,采用 Varint 表示法,大的数字则需要 5 个 byte 来表示。从统计的角度来说,一般不会所有的消息中的数字都是大数,因此大多数情况下,采用 Varint 后,可以用更少的字节数来表示数字信息。下面就详细介绍一下 Varint。

Varint 中的每个 byte 的最高位 bit 有特殊的含义,如果该位为 1,表示后续的 byte 也是该数字的一部分,如果该位为 0,则结束。其他的 7 个 bit 都用来表示数字。因此小于 128 的数字都可以用一个 byte 表示。大于 128 的数字,比如 300,会用两个字节来表示:1010 1100 0000 0010

下图演示了 Google Protocol Buffer 如何解析两个 bytes。注意到最终计算前将两个 byte 的位置相互交换过一次,这是因为 Google Protocol Buffer 字节序采用 little-endian 的方式。

字段 a(int32)

第一个 key 00001000 的前 5 位是 1,表明是字段 a;后 3 位 wire_type 是 0,所以是 Varint(一种变长的数值类型,在表示小数值的数时更省空间)。

第一个 value 是 00000001,所以是 a = 1

字段 b(int64)

代码语言:javascript
复制
key             00010000
field_number    2               b
wire_type       0               Varint
value           00000010        2

字段 c(fixed64,定长)

代码语言:javascript
复制
key             00011001
field_number    3               c
wire_type       1               64-bit
value           3               00000011
                                00000000
                                00000000
                                00000000
                                00000000
                                00000000
                                00000000
                                00000000

字段 d(string)

代码语言:javascript
复制
key             00100010
field_number    4               d
wire_type       2               Length-delimited
value           java            00000100(length:4)
                                01101010 j
                                01100001 a
                                01110110 v
                                01100001 a

与 JSON 和 XML 序列化对比

JSON

上面的代码,用 JSON 来表示是这样的:

代码语言:javascript
复制
{"a":"1","b":"2","c":"3","d":"java"}

代码测试长度:

代码语言:javascript
复制
@Test
public void testJSON() {
    String json = "{\"a\":\"1\",\"b\":\"2\",\"c\":\"3\",\"d\":\"java\"}";
    System.out.println(json.getBytes().length);
}

结果是 36 个字节,而 protobuf 生成的字节码仅有 18 个字节。

代码语言:javascript
复制
说明:本例中的 protobuf 字段 c 使用了 fixed64 类型,否则还能够省 5 个字节。protobuf 还有一个非常明显的好处就是:长度与字段的名字无关。如果字段 a 叫做 apple,那么 protobuf 序列化后仍然是 18 个字节,而 JSON 则要增加 4 个字节。

XML

XML 就不必说了,ε=(´ο`*)))

Google Protocol Buffers 的一点思考

  1. 该协议开发过程确实有些繁琐,因为要编写 proto 文件,并生成对应语言的代码;同时因为是字节码,并不容易读取(需要将其转换成对象,打印对象)。如果要更改协议,需要频繁修改文件及代码。
  2. 但是正是因为这种约束,Protocol Buffers 具有非常强的版本兼容性。换做是 JSON,虽然很灵活,但是完全没有约束~~~
  3. Protocol Buffers 序列化的字节码非常非常小,速度很快,是游戏开发的首选。
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018.05.31 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 说明
  • 编写 proto 文件
  • 测试字节码
  • 生成的字节码
  • 分析字节码
    • varint
      • 字段 a(int32)
        • 字段 b(int64)
          • 字段 c(fixed64,定长)
            • 字段 d(string)
            • 与 JSON 和 XML 序列化对比
              • JSON
                • XML
                • Google Protocol Buffers 的一点思考
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档