JProtobuf
一、简介
本文介绍另一种 JAVA 对象序列化神器——ProtoBuf(Protocol Buffers)。ProtoBuf 是由 Google 开发的一种用于序列化结构化数据的高效、灵活且语言中立的协议。它被广泛应用于数据通信、数据存储、RPC(远程过程调用)等场景,尤其适用于分布式系统和微服务架构中。ProtoBuf 序列化后的数据体积通常比 JSON、XML 小很多,能够显著减少带宽和存储空间的占用,同时解析速度也更快。然而,相较于 JSON、XML 等文本格式,ProtoBuf 的可读性较差,使用也相对繁琐,主要因为需要使用 protoc
编译 .proto
文件。本文将介绍 JProtobuf 这一神器,帮助开发者像使用 JSON 一样简便地使用 ProtoBuf。
二、快速入门
JProtobuf 是一个基于 Java 的 ProtoBuf 序列化与反序列化工具,由百度开发并开源,旨在简化 ProtoBuf 在 Java 项目中的使用门槛。JProtobuf 提供了一种更符合 Java 开发者习惯的方式来定义和操作 ProtoBuf 数据,无需编写 .proto
文件和使用编译工具。项目的 GitHub 地址如下:
使用 JProtobuf 只需三步即可完成 Java 对象与 ProtoBuf 数据之间的相互转换:
第一步:添加 Maven 依赖
在项目的 pom.xml
文件中添加以下依赖:
<dependency>
<groupId>com.baidu</groupId>
<artifactId>jprotobuf</artifactId>
<version>2.4.23</version>
</dependency>
第二步:为对象添加 @ProtobufClass
注解
创建一个 Java 类,并使用 @ProtobufClass
注解标记:
import java.util.Date;
import com.baidu.bjf.remoting.protobuf.annotation.ProtobufClass;
import lombok.Data;
@Data
@ProtobufClass
public class User {
private Long id;
private String name;
private String trueName;
private Integer age;
private String sex;
private Date createTime;
}
第三步:创建 ProtobufProxy 并使用
编写测试代码,实现对象的序列化与反序列化:
package com.litongjava.file.model;
import java.io.IOException;
import java.util.Date;
import org.junit.Test;
import com.baidu.bjf.remoting.protobuf.Codec;
import com.baidu.bjf.remoting.protobuf.ProtobufProxy;
public class UserTest {
@Test
public void test() {
User user = new User();
user.setId(1L);
user.setName("赵侠客");
user.setAge(29);
user.setSex("男");
user.setTrueName("公众号");
user.setCreateTime(new Date());
// 创建 JProtobuf 代理
Codec<User> codec = ProtobufProxy.create(User.class);
// 使用 ProtoBuf 序列化
byte[] bytes = null;
try {
bytes = codec.encode(user);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(bytes.length); // 输出:38
// 使用 ProtoBuf 反序列化
try {
User user1 = codec.decode(bytes);
System.out.println(user1);
} catch (IOException e) {
e.printStackTrace();
}
}
}
通过上述代码可以看出,使用 JProtobuf 完成对象的序列化与反序列化与使用 JSON 类似简便。其中,ProtoBuf 序列化后的字节长度为 38,而 JSON 的字节长度为 98,节省了约 61% 的空间。当然,并非所有对象在使用 ProtoBuf 后的大小都能与 JSON 相差如此显著,接下来我们将对比中等和大规模 JSON 的序列化大小差异。
三、大小对比
3.1 中等 JSON 大小对比
本文将中等 JSON 定义为包含 20 个用户的数组。由于 JProtobuf 不直接支持 List
对象,需要封装成一个 Users
对象:
@Data
@ProtobufClass
public class Users {
private List<User> users;
private Long id;
}
使用上述 20 个用户创建一个中等 JSON:
// 模拟 20 个用户的中等 JSON
List<User> userList = new ArrayList<>();
IntStream.range(0, 20).forEach(i -> {
User user2 = new User();
BeanUtil.copyProperties(user, user2);
userList.add(user2);
});
users.setUsers(userList);
// 使用 JProtobuf 序列化中等 JSON
Codec<Users> userListCodec = ProtobufProxy.create(Users.class);
String mediumJson = JSON.toJSONString(users);
byte[] mediumProtobuf = userListCodec.encode(users);
System.out.println(mediumJson.getBytes().length + "\t" + mediumProtobuf.length);
// 输出示例:1991 800
结果显示,中等 JSON 使用 ProtoBuf 序列化后的长度为 800 字节,JSON 序列化后的长度为 1991 字节,ProtoBuf 比 JSON 节省了约 60%。
3.2 大规模 JSON 大小对比
大规模 JSON 定义为包含富文本 HTML 数据的稿件正文:
@Data
@ProtobufClass
public class Article {
private Long id;
private String author;
private Long tenantId;
private String title;
private String subTitle;
private String htmlContent;
private Date publishTime;
}
模拟大规模 JSON 数据:
// 模拟大规模 JSON 的稿件正文
Article article = new Article();
article.setId(10000L);
article.setTenantId(10000L);
article.setAuthor("公众号:赵侠客");
article.setPublishTime(new Date());
article.setTitle(RandomUtil.randomString("主标题", 100));
article.setSubTitle(RandomUtil.randomString("副标题", 50));
// 公众号文章字符串长度为 89544
article.setHtmlContent(new String(Files.readAllBytes(Paths.get("article.html"))));
// 使用 JProtobuf 序列化大规模 JSON
Codec<Article> articleCodec = ProtobufProxy.create(Article.class);
String bigJson = JSON.toJSONString(article);
byte[] bigProtobuf = articleCodec.encode(article);
System.out.println(bigJson.getBytes().length + "\t" + bigProtobuf.length);
// 输出示例:94595 92826
对于大规模 JSON,ProtoBuf 序列化后的大小仅比 JSON 小约 2%,这是因为在大数据量情况下,JSON 格式中的冗余信息占总信息的比例较小,因此 ProtoBuf 与 JSON 的大小差异也不显著。
四、性能对比
性能对比参考前文《FastJson、Jackson、Gson、Hutool,JSON 解析哪家强?JMH 基准测试来排行》。为了准确评估,我们使用 JMH 基准测试,从小 JSON、中等 JSON、大规模 JSON 的序列化和反序列化六项指标进行对比。本次测试选用性能领先的 FastJson2 与 ProtoBuf 进行对比。
百分制评分:以 FastJson2 的性能作为 100 分的基准,ProtoBuf 相对 FastJson2 的性能分数大于 100 分则表示更快。
指标缩写定义:
- SS:小 JSON 序列化得分
- MS:中等 JSON 序列化得分
- BS:大规模 JSON 序列化得分
- SDS:小 JSON 反序列化得分
- MDS:中等 JSON 反序列化得分
- BDS:大规模 JSON 反序列化得分
- 变化:相对序列化得分的变化
4.1 小 JSON 序列化
工具 | 分数 | 百分制 |
---|---|---|
FastJson2 | 13,561,505 | 100 |
ProtoBuf | 12,858,532 | 94.8 |
可以看出,ProtoBuf 在小对象序列化性能上几乎与 FastJson2 持平,表现非常出色。
4.2 中等 JSON 序列化
工具 | 分数 | 百分制 |
---|---|---|
FastJson2 | 825,644 | 100 |
ProtoBuf | 436,635 | 52.9 |
在中等 JSON 的数组对象序列化性能上,ProtoBuf 仅为 FastJson2 的一半,可能与 JProtobuf 不直接支持 List
对象有关,因此性能表现不够理想。
4.3 大规模 JSON 序列化
工具 | 分数 | 百分制 |
---|---|---|
FastJson2 | 10,086 | 100 |
ProtoBuf | 21,764 | 215.8 |
在大规模 JSON 的序列化性能上,ProtoBuf 表现优异,速度是 FastJson2 的两倍多,远超传统 JSON 工具。
4.4 小 JSON 反序列化
工具 | 百分制 | 变化 | SDS | SS |
---|---|---|---|---|
FastJson2 | 100 | -41.7% | 7,921,069 | 13,561,505 |
ProtoBuf | 185.3 | +14.1% | 14,676,712 | 12,858,532 |
小对象的反序列化,ProtoBuf 比 FastJson2 快近一倍,达到 185.3 分。此外,ProtoBuf 的反序列化性能还优于其序列化性能 14.1%,而 FastJson2 的反序列化性能比序列化性能低了 41.7%,这显示出 ProtoBuf 在反序列化方面的强大性能。
4.5 中等 JSON 反序列化
工具 | 百分制 | 变化 | MDS | MS |
---|---|---|---|---|
FastJson2 | 100 | -53.3% | 385,392 | 825,644 |
ProtoBuf | 79.4 | -29.9% | 306,087 | 436,635 |
在数组对象的反序列化和序列化性能上,ProtoBuf 的表现未能显著超越 FastJson2,显示出在处理集合对象时,ProtoBuf 仍有提升空间。
4.6 大规模 JSON 反序列化
工具 | 百分制 | 变化 | BDS | BS |
---|---|---|---|---|
FastJson2 | 100 | -35.7% | 6,487 | 10,086 |
ProtoBuf | 480.4 | +43.2% | 31,163 | 21,764 |
在大规模 JSON 的反序列化性能上,ProtoBuf 表现惊人,达到了 FastJson2 的近五倍,达到 480.4 分。这可能不仅仅是因为 ProtoBuf 的反序列化性能强劲,还因为 JSON 数据结构在大数据量下并不适合高效转换。
五、总结
5.1 空间占用
ProtoBuf 相较于 JSON 的空间占用变化如下:
对象类型 | 相比 JSON |
---|---|
小 JSON | -61% |
中等 JSON | -60% |
大规模 JSON | -2% |
5.2 时间性能
ProtoBuf 在序列化和反序列化性能方面的综合表现如下:
工具 | 排名 | 总分 | 百分制 | SS | MS | BS | SDS | MDS | BDS |
---|---|---|---|---|---|---|---|---|---|
ProtoBuf | 王者 | 1,108.6 | 195.5 | 94.8 | 52.9 | 215.8 | 185.3 | 79.4 | 480.4 |
FastJson2 | 状元 | 567 | 100 | 100 | 100 | 72.0 | 100 | 100 | 95.0 |
FastJson | 榜眼 | 394.2 | 69.5 | 62.3 | 73.2 | 35.8 | 51.3 | 71.6 | 100 |
Jackson | 探花 | 342 | 60.3 | 42.3 | 89.7 | 100 | 27.4 | 31.3 | 51.3 |
Gson | 进士 | 188.2 | 33.2 | 8.9 | 21.5 | 43.6 | 20.7 | 25.3 | 68.2 |
Hutool | 孙山 | 42.2 | 7.4 | 3.2 | 4.6 | 7.7 | 7.3 | 5.5 | 13.9 |
图示:ProtoBuf VS FastJson2 雷达图
5.3 结论
通过上述测试,可以得出以下关于 ProtoBuf 的结论:
空间占用:
- 在小数据情况下,ProtoBuf 序列化后的数据空间相比 JSON 大幅缩小,本文测试达到约 60% 的节省。
- 在大数据情况下,ProtoBuf 序列化后的数据空间与 JSON 相差无几,仅有约 2% 的差异。
时间性能:
- 在小数据的序列化性能上,ProtoBuf 可与 FastJson2 相媲美。
- 在数组对象的序列化和反序列化性能上,ProtoBuf 略逊于 FastJson2。
- 在大数据的序列化和反序列化性能上,ProtoBuf 显著优于所有 JSON 工具。
综合来看,ProtoBuf 在序列化后的空间缩减和性能提升方面相较于 JSON 有明显优势。因此,在 RPC 场景中,将序列化和反序列化从 JSON 切换为 ProtoBuf,可以为系统带来显著的性能提升。
六、tio-boot Controller 返回 ProtoBuf 字节
本文还将介绍如何在 tio-boot 框架中,通过 Controller 返回 ProtoBuf 序列化后的字节数据。
示例数据
package com.litongjava.file.controller;
import java.io.IOException;
import java.util.Date;
import com.baidu.bjf.remoting.protobuf.Codec;
import com.baidu.bjf.remoting.protobuf.ProtobufProxy;
import com.litongjava.annotation.RequestPath;
import com.litongjava.file.model.User;
@RequestPath("/user")
public class UserController {
public byte[] index() throws IOException {
User user = new User();
user.setId(1L);
user.setName("赵侠客");
user.setAge(29);
user.setSex("男");
user.setTrueName("公众号");
user.setCreateTime(new Date());
// 创建 JProtobuf 代理
Codec<User> codec = ProtobufProxy.create(User.class);
// 使用 ProtoBuf 序列化
return codec.encode(user);
}
}
测试访问
启动应用后,通过以下 URL 访问 Controller:
http://localhost/user/index
访问该接口将返回 ProtoBuf 序列化后的字节数据。
七、参考资料
通过本文的介绍,开发者可以快速上手 JProtobuf,实现高效的 Java 对象与 ProtoBuf 数据之间的转换,并在实际项目中应用 ProtoBuf 以提升系统的性能和资源利用率。