ProtoBuf试用与JSON的比较

Stella981
• 阅读 851

介绍

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:%s\n", 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)

结果,是可以转换成功。

END QQ群:559259945

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
java将前端的json数组字符串转换为列表
记录下在前端通过ajax提交了一个json数组的字符串,在后端如何转换为列表。前端数据转化与请求varcontracts{id:'1',name:'yanggb合同1'},{id:'2',name:'yanggb合同2'},{id:'3',name:'yang
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
Python 转化成 PB 格式数据
一、概述ProtocolBuffers是Google公司开发的一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或RPC数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。ProtocolBuffers简称为protobuf或pb,下面
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
为什么mysql不推荐使用雪花ID作为主键
作者:毛辰飞背景在mysql中设计表的时候,mysql官方推荐不要使用uuid或者不连续不重复的雪花id(long形且唯一),而是推荐连续自增的主键id,官方的推荐是auto_increment,那么为什么不建议采用uuid,使用uuid究
liam liam
10个月前
探索 RPC 与 Protobuf 的强大功能
一、Protobuf数据格式简析Protobuf是什么?在数据密集型应用领域,Google开发的Protobuf作为一种高效数据编码方式而广受欢迎。它胜任于及XML对比,不仅在体积和速度上表现出色,而且其结构化方式优化了网络传输中的性能。简而言之,Prot