关于序列化:序列化ProtoBuf-与-JSON-的比较

40次阅读

共计 3660 个字符,预计需要花费 10 分钟才能阅读完成。

介绍

ProtoBuf 是 google 团队开发的用于高效存储和读取结构化数据的工具。什么是结构化数据呢,正如字面上表白的,就是带有肯定构造的数据。比方电话簿上有很多记录数据,每条记录蕴含姓名、ID、邮件、电话等,这种构造反复呈现。

同类

XML、JSON 也能够用来存储此类结构化数据,然而应用 ProtoBuf 示意的数据能更加高效,并且将数据压缩得更小。

原理

ProtoBuf 是通过 ProtoBuf 编译器将与编程语言无关的特有的 .proto 后缀的数据结构文件编译成各个编程语言 (Java,C/C++,Python) 专用的类文件, 而后通过 Google 提供的各个编程语言的反对库 lib 即可调用 API。(对于 proto 构造体怎么编写,可自行查阅文档)

ProtoBuf 编译器装置

Mac : brew install protobuf

举个例子

1. 先创立一个 proto 文件
message.proto

syntax = "proto3";

message Person {
    int32 id = 1;
    string name = 2;

    repeated Phone phone = 4;

    enum PhoneType {
        MOBILE = 0;
        HOME = 1;
        WORK = 2;
    }

    message Phone {
        string number = 1;
        PhoneType type = 2;
    }
} 

2. 创立一个 Java 我的项目
并且将 proto 文件搁置 src/main/proto 文件夹下

3. 编译 proto 文件至 Java 版本
用命令行 cd 到 src/main 目录下

终端执行命令 : protoc –java_out=./java ./proto/*.proto

会发现,在你的 src/main/java 里曾经生成里对应的 Java 类

4. 依赖 Java 版本的 ProtoBuf 反对库
这里只举一个用 Gradle 应用依赖的栗子

implementation 'com.google.protobuf:protobuf-java:3.9.1' 

5. 将 Java 对象转为 ProtoBuf 数据

Message.Person.Phone.Builder phoneBuilder = Message.Person.Phone.newBuilder();
Message.Person.Phone phone1 = phoneBuilder
        .setNumber("100860")
        .setType(Message.Person.PhoneType.HOME)
        .build();
Message.Person.Phone phone2 = phoneBuilder
        .setNumber("100100")
        .setType(Message.Person.PhoneType.MOBILE)
        .build();
Message.Person.Builder personBuilder = Message.Person.newBuilder();
personBuilder.setId(1994);
personBuilder.setName("XIAOLEI");
personBuilder.addPhone(phone1);
personBuilder.addPhone(phone2);

Message.Person person = personBuilder.build();
long old = System.currentTimeMillis();
byte[] buff = person.toByteArray();
System.out.println("ProtoBuf 编码耗时:" + (System.currentTimeMillis() - old));
System.out.println(Arrays.toString(buff));
System.out.println("ProtoBuf 数据长度:" + buff.length); 

6. 将 ProtoBuf 数据,转换回 Java 对象

System.out.println("- 开始解码 -");
old = System.currentTimeMillis();
Message.Person personOut = Message.Person.parseFrom(buff);
System.out.println("ProtoBuf 解码耗时:" + (System.currentTimeMillis() - old));
System.out.printf("Id:%d, Name:%sn", personOut.getId(), personOut.getName());
List<Message.Person.Phone> phoneList = personOut.getPhoneList();
for (Message.Person.Phone phone : phoneList)
{System.out.printf("手机号:%s (%s)n", phone.getNumber(), phone.getType());
} 

比拟

为了能体现 ProtoBuf 的劣势,我写了同样构造体的 Java 类,并且将 Java 对象转换成 JSON 数据,来与 ProtoBuf 进行比拟。JSON 编译库应用 Google 提供的 GSON 库,JSON 的局部代码就不贴出来了,间接展现后果

比拟后果

运行 1 次

【JSON 开始编码】JSON 编码 1 次,耗时:22ms
JSON 数据长度:106
- 开始解码 -
JSON 解码 1 次,耗时:1ms【ProtoBuf 开始编码】ProtoBuf 编码 1 次, 耗时:32ms
ProtoBuf 数据长度:34
- 开始解码 -
ProtoBuf 解码 1 次, 耗时:3ms 

运行 10 次

【JSON 开始编码】JSON 编码 10 次,耗时:22ms
JSON 数据长度:106
- 开始解码 -
JSON 解码 10 次,耗时:4ms【ProtoBuf 开始编码】ProtoBuf 编码 10 次, 耗时:29ms
ProtoBuf 数据长度:34
- 开始解码 -
ProtoBuf 解码 10 次, 耗时:3ms 

运行 100 次

【JSON 开始编码】JSON 编码 100 次,耗时:32ms
JSON 数据长度:106
- 开始解码 -
JSON 解码 100 次,耗时:8ms【ProtoBuf 开始编码】ProtoBuf 编码 100 次, 耗时:31ms
ProtoBuf 数据长度:34
- 开始解码 -
ProtoBuf 解码 100 次, 耗时:4ms 

运行 1000 次

【JSON 开始编码】JSON 编码 1000 次,耗时:39ms
JSON 数据长度:106
- 开始解码 -
JSON 解码 1000 次,耗时:21ms【ProtoBuf 开始编码】ProtoBuf 编码 1000 次, 耗时:37ms
ProtoBuf 数据长度:34
- 开始解码 -
ProtoBuf 解码 1000 次, 耗时:8ms 

运行 1 万 次

【JSON 开始编码】JSON 编码 10000 次,耗时:126ms
JSON 数据长度:106
- 开始解码 -
JSON 解码 10000 次,耗时:93ms【ProtoBuf 开始编码】ProtoBuf 编码 10000 次, 耗时:49ms
ProtoBuf 数据长度:34
- 开始解码 -
ProtoBuf 解码 10000 次, 耗时:23ms 

运行 10 万 次

【JSON 开始编码】JSON 编码 100000 次,耗时:248ms
JSON 数据长度:106
- 开始解码 -
JSON 解码 100000 次,耗时:180ms【ProtoBuf 开始编码】ProtoBuf 编码 100000 次, 耗时:51ms
ProtoBuf 数据长度:34
- 开始解码 -
ProtoBuf 解码 100000 次, 耗时:58ms 

总结

编解码性能
上述栗子只是简略的采样,实际上据我的试验发现

  • 次数在 1 千以下,ProtoBuf 的编码与解码性能,都与 JSON 并驾齐驱,甚至还有比 JSON 差的趋势。
  • 次数在 2 千以上,ProtoBuf 的编码解码性能,都比 JSON 高出很多。
  • 次数在 10 万以上,ProtoBuf 的编解码性能就很显著了,远远高出 JSON 的性能。

内存占用
ProtoBuf 的内存 34,而 JSON 达到 106,ProtoBuf 的内存占用只有 JSON 的 1 /3.

结尾

其实这次试验有很多可待优化的中央,就算是这种粗略的测试,也能看进去 ProtoBuf 的劣势。

兼容

新增字段

  • 在 proto 文件中新增 nickname 字段
  • 生成 Java 文件
  • 用老 proto 字节数组数据,转换成对象
Id:1994, Name:XIAOLEI
手机号:100860 (HOME)
手机号:100100 (MOBILE)
getNickname= 

后果,是能够转换胜利。

删除字段

  • 在 proto 文件中删除 name 字段
  • 生成 Java 文件
  • 用老 proto 字节数组数据,转换成对象
Id:1994, Name:null
手机号:100860 (HOME)
手机号:100100 (MOBILE) 

后果,是能够转换胜利。

起源:my.oschina.net/xiaolei123/blog/3085607

欢送关注我的微信公众号「码农解围」,分享 Python、Java、大数据、机器学习、人工智能等技术,关注码农技术晋升•职场解围•思维跃迁,20 万 + 码农成长充电第一站,陪有幻想的你一起成长

正文完
 0