Skip to content

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 GitHub

使用 JProtobuf 只需三步即可完成 Java 对象与 ProtoBuf 数据之间的相互转换:

第一步:添加 Maven 依赖 ​

在项目的 pom.xml 文件中添加以下依赖:

xml
<dependency>
    <groupId>com.baidu</groupId>
    <artifactId>jprotobuf</artifactId>
    <version>2.4.23</version>
</dependency>

第二步:为对象添加 @ProtobufClass 注解 ​

创建一个 Java 类,并使用 @ProtobufClass 注解标记:

java
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 并使用 ​

编写测试代码,实现对象的序列化与反序列化:

java
package nexus.io.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 对象:

java
@Data
@ProtobufClass
public class Users {
    private List<User> users;
    private Long id;
}

使用上述 20 个用户创建一个中等 JSON:

java
// 模拟 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 数据的稿件正文:

java
@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 数据:

java
// 模拟大规模 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 序列化 ​

工具分数百分制
FastJson213,561,505100
ProtoBuf12,858,53294.8

可以看出,ProtoBuf 在小对象序列化性能上几乎与 FastJson2 持平,表现非常出色。

4.2 中等 JSON 序列化 ​

工具分数百分制
FastJson2825,644100
ProtoBuf436,63552.9

在中等 JSON 的数组对象序列化性能上,ProtoBuf 仅为 FastJson2 的一半,可能与 JProtobuf 不直接支持 List 对象有关,因此性能表现不够理想。

4.3 大规模 JSON 序列化 ​

工具分数百分制
FastJson210,086100
ProtoBuf21,764215.8

在大规模 JSON 的序列化性能上,ProtoBuf 表现优异,速度是 FastJson2 的两倍多,远超传统 JSON 工具。

4.4 小 JSON 反序列化 ​

工具百分制变化SDSSS
FastJson2100-41.7%7,921,06913,561,505
ProtoBuf185.3+14.1%14,676,71212,858,532

小对象的反序列化,ProtoBuf 比 FastJson2 快近一倍,达到 185.3 分。此外,ProtoBuf 的反序列化性能还优于其序列化性能 14.1%,而 FastJson2 的反序列化性能比序列化性能低了 41.7%,这显示出 ProtoBuf 在反序列化方面的强大性能。

4.5 中等 JSON 反序列化 ​

工具百分制变化MDSMS
FastJson2100-53.3%385,392825,644
ProtoBuf79.4-29.9%306,087436,635

在数组对象的反序列化和序列化性能上,ProtoBuf 的表现未能显著超越 FastJson2,显示出在处理集合对象时,ProtoBuf 仍有提升空间。

4.6 大规模 JSON 反序列化 ​

工具百分制变化BDSBS
FastJson2100-35.7%6,48710,086
ProtoBuf480.4+43.2%31,16321,764

在大规模 JSON 的反序列化性能上,ProtoBuf 表现惊人,达到了 FastJson2 的近五倍,达到 480.4 分。这可能不仅仅是因为 ProtoBuf 的反序列化性能强劲,还因为 JSON 数据结构在大数据量下并不适合高效转换。

五、总结 ​

5.1 空间占用 ​

ProtoBuf 相较于 JSON 的空间占用变化如下:

对象类型相比 JSON
小 JSON-61%
中等 JSON-60%
大规模 JSON-2%

5.2 时间性能 ​

ProtoBuf 在序列化和反序列化性能方面的综合表现如下:

工具排名总分百分制SSMSBSSDSMDSBDS
ProtoBuf王者1,108.6195.594.852.9215.8185.379.4480.4
FastJson2状元56710010010072.010010095.0
FastJson榜眼394.269.562.373.235.851.371.6100
Jackson探花34260.342.389.710027.431.351.3
Gson进士188.233.28.921.543.620.725.368.2
Hutool孙山42.27.43.24.67.77.35.513.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 序列化后的字节数据。

示例数据 ​

java
package nexus.io.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 nexus.io.annotation.RequestPath;
import nexus.io.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 以提升系统的性能和资源利用率。

MIT Licensed