杰瑞科技汇

Protobuf Java序列化如何提升性能与兼容性?

这篇指南将涵盖从零开始的完整流程,包括:

Protobuf Java序列化如何提升性能与兼容性?-图1
(图片来源网络,侵删)
  1. 环境准备:安装和配置 Protobuf 编译器 (protoc) 和 Java Maven 依赖。
  2. 定义 .proto 文件:创建你的数据结构定义。
  3. 生成 Java 代码:使用 protoc 编译器生成 Java 类。
  4. 序列化:将 Java 对象转换为字节数组。
  5. 反序列化:将字节数组还原为 Java 对象。
  6. 进阶用法:如处理嵌套对象、集合、枚举等。

环境准备

在开始之前,你需要确保你的开发环境已经配置好 Protobuf。

a. 安装 Protobuf 编译器 (protoc)

你需要从 Protocol Buffers 的官方 GitHub 发布页面 下载适合你操作系统的 protoc

以 macOS 为例 (使用 Homebrew):

brew install protobuf

验证安装:

Protobuf Java序列化如何提升性能与兼容性?-图2
(图片来源网络,侵删)
protoc --version
# 应该输出类似 libprotoc 3.21.12 的信息

b. 配置 Java Maven 项目

在你的 pom.xml 文件中,添加 protobuf-javaprotobuf-java-util 依赖。protobuf-java 是核心库,而 protobuf-java-util 提供了一些有用的工具,JsonFormat

<dependencies>
    <!-- Protocol Buffers Core Library -->
    <dependency>
        <groupId>com.google.protobuf</groupId>
        <artifactId>protobuf-java</artifactId>
        <version>3.24.4</version> <!-- 使用最新的稳定版本 -->
    </dependency>
    <!-- Optional: For JSON support and other utilities -->
    <dependency>
        <groupId>com.google.protobuf</groupId>
        <artifactId>protobuf-java-util</artifactId>
        <version>3.24.4</version>
    </dependency>
</dependencies>

定义 .proto 文件

这是整个流程的起点,你需要创建一个 .proto 文件来定义你的数据结构,我们创建一个名为 user.proto 的文件。

src/main/resources/user.proto

// 指定使用的 proto 版本
syntax = "proto3";
// 指定生成的 Java 类的包名
option java_package = "com.example.models";
// 指定生成的外部类名,如果不指定,默认是文件名(首字母大写)
option java_outer_classname = "UserProto";
// 定义一个消息,类似于 Java 中的类
message User {
  // 字段类型 字段名 = 字段编号;
  // 字段编号是唯一的,在修改 .proto 文件时不要轻易改动
  int32 id = 1;
  string name = 2;
  string email = 3;
  // 嵌套的消息
  message Address {
    string street = 1;
    string city = 2;
    int32 zip_code = 3;
  }
  // 可选的嵌套对象
  optional Address address = 4; // 在 proto3 中,optional 已被废弃,推荐使用 oneof 或包装类型
  // 使用 oneof 来确保只有一个字段被设置
  oneof contact_info {
    string phone_number = 5;
    string fax_number = 6;
  }
}

关键字说明:

Protobuf Java序列化如何提升性能与兼容性?-图3
(图片来源网络,侵删)
  • syntax = "proto3": 使用 Protocol Buffers 的第 3 版语法。
  • message: 定义一个结构,类似于 Java 的 class 或 POJO。
  • int32, string: 数据类型,会自动映射到 Java 的 int, String
  • = 1, = 2: 字段编号,这是 Protobuf 的核心,用于标识字段,序列化时只存储字段编号和值,不存储字段名,所以非常高效。一旦定义,就不要修改,否则会破坏兼容性。
  • optional: 在 proto2 中表示字段可以不存在,在 proto3 中,所有基本类型字段都是可选的(默认为0或空字符串),但 optional 关键字已被废弃,对于需要明确“未设置”语义的字段(如 int32),推荐使用 google.protobuf.Int32Value 这样的包装类型。
  • oneof: 确保一组字段中只有一个被设置。contact_info 要么是 phone_number,要么是 fax_number,但不能同时是两者。
  • option java_package: 生成的 Java 类的包。
  • option java_outer_classname: 生成的最外层 Java 类的类名,这个类将包含所有内部消息类(如 User, Address)的静态类。

生成 Java 代码

你需要使用 protoc 编译器来根据 .proto 文件生成 Java 代码。

在项目根目录下执行以下命令:

# -I: 指定 .proto 文件所在的目录
# --java_out: 指定 Java 代码的输出目录
# 最后是 .proto 文件的路径
protoc -I=src/main/resources --java_out=src/main/java src/main/resources/user.proto

执行成功后,你会在 src/main/java/com/example/models/ 目录下找到一个名为 UserProto.java 的文件。这个文件是自动生成的,你绝对不应该手动编辑它!

打开 UserProto.java,你会看到类似这样的结构:

  • 一个 public final class UserProto
  • 内部有 public static final class User extends GeneratedMessageV3 类,这就是你用来操作的对象。
  • 内部有 public static final class Address extends GeneratedMessageV3 类。
  • 各种 Builder 类,用于构建 User 对象。

序列化 - 将 Java 对象转为字节数组

序列化的过程就是创建一个 User 对象,然后调用它的 toByteArray() 方法。

import com.example.models.UserProto;
import java.io.IOException;
public class SerializationExample {
    public static void main(String[] args) {
        // 1. 创建一个 User 对象(通过 Builder 模式)
        UserProto.User user = UserProto.User.newBuilder()
                .setId(101)
                .setName("Alice")
                .setEmail("alice@example.com")
                .setAddress(UserProto.User.Address.newBuilder()
                        .setStreet("123 Main St")
                        .setCity("Wonderland")
                        .setZipCode(12345)
                        .build())
                .setPhoneNumber("123-456-7890") // 设置 oneof 字段
                .build();
        // 2. 序列化为字节数组
        byte[] serializedBytes = user.toByteArray();
        System.out.println("序列化成功!字节数组长度: " + serializedBytes.length);
        // 你可以将这个字节数组写入文件、通过网络发送等
        //  Files.write(Paths.get("user.dat"), serializedBytes);
    }
}

反序列化 - 将字节数组还原为 Java 对象

反序列化的过程就是调用 UserProto.User.parseFrom() 方法,并传入之前生成的字节数组。

import com.example.models.UserProto;
import java.io.IOException;
public class DeserializationExample {
    public static void main(String[] args) throws IOException {
        // 假设这是从某个地方接收到的字节数组
        byte[] serializedBytes = getSerializedBytes(); // 我们复用上面的序列化结果
        // 1. 从字节数组反序列化
        UserProto.User deserializedUser = UserProto.User.parseFrom(serializedBytes);
        // 2. 访问反序列化后的对象数据
        System.out.println("反序列化成功!");
        System.out.println("ID: " + deserializedUser.getId());
        System.out.println("Name: " + deserializedUser.getName());
        System.out.println("Email: " + deserializedUser.getEmail());
        if (deserializedUser.hasAddress()) {
            System.out.println("Address: " + deserializedUser.getAddress().getStreet() + ", " + deserializedUser.getAddress().getCity());
        }
        if (deserializedUser.getPhoneNumber().isEmpty()) {
            System.out.println("Phone Number: " + deserializedUser.getFaxNumber());
        } else {
            System.out.println("Phone Number: " + deserializedUser.getPhoneNumber());
        }
    }
    // 模拟获取字节数组
    private static byte[] getSerializedBytes() {
        UserProto.User user = UserProto.User.newBuilder()
                .setId(101)
                .setName("Alice")
                .setEmail("alice@example.com")
                .setAddress(UserProto.User.Address.newBuilder()
                        .setStreet("123 Main St")
                        .setCity("Wonderland")
                        .setZipCode(12345)
                        .build())
                .setPhoneNumber("123-456-7890")
                .build();
        return user.toByteArray();
    }
}

进阶用法

a. 处理集合 (Lists)

Protobuf 中,repeated 关键字用来定义数组或列表。

user.proto 中添加:

repeated string hobbies = 7;

在 Java 代码中使用:

// 序列化时
UserProto.User user = UserProto.User.newBuilder()
        // ... 其他字段
        .addHobby("Reading")
        .addHobby("Hiking")
        .addHobby("Coding")
        .build();
// 反序列化后访问
for (String hobby : deserializedUser.getHobbiesList()) {
    System.out.println("Hobby: " + hobby);
}

b. 处理枚举

.proto 文件中定义枚举。

user.proto 中添加:

enum Gender {
  GENDER_UNSPECIFIED = 0;
  MALE = 1;
  FEMALE = 2;
  OTHER = 3;
}
message User {
  // ... 其他字段
  Gender gender = 8;
}

在 Java 代码中使用:

// 序列化时
UserProto.User user = UserProto.User.newBuilder()
        // ... 其他字段
        .setGender(UserProto.Gender.MALE)
        .build();
// 反序列化后访问
UserProto.Gender gender = deserializedUser.getGender();
System.out.println("Gender: " + gender); // 输出 MALE

c. JSON 格式转换 (protobuf-java-util)

利用 JsonFormat 可以轻松地在 Protobuf 消息和 JSON 字符串之间转换。

import com.google.protobuf.util.JsonFormat;
import com.example.models.UserProto;
public class JsonFormatExample {
    public static void main(String[] args) throws Exception {
        // 1. 创建一个 User 对象
        UserProto.User user = UserProto.User.newBuilder()
                .setId(101)
                .setName("Bob")
                .setEmail("bob@example.com")
                .build();
        // 2. 将 Protobuf 消息转换为 JSON 字符串
        String jsonString = JsonFormat.printer().omittingInsignificantWhitespace().print(user);
        System.out.println("JSON 格式:");
        System.out.println(jsonString);
        // 输出: {"id":101,"name":"Bob","email":"bob@example.com"}
        // 3. 将 JSON 字符串解析回 Protobuf 消息
        UserProto.User.Builder userBuilder = UserProto.User.newBuilder();
        JsonFormat.parser().merge(jsonString, userBuilder);
        UserProto.User userFromJson = userBuilder.build();
        System.out.println("\n从 JSON 解析后的 ID: " + userFromJson.getId());
    }
}
操作 核心方法 描述
序列化 message.toByteArray() GeneratedMessageV3 对象转换为 byte[]
反序列化 Message.parseFrom(byte[]) byte[] 创建 GeneratedMessageV3 对象。
构建对象 Message.newBuilder() 使用 Builder 模式创建和修改消息对象。
JSON 转换 JsonFormat.printer() / JsonFormat.parser() 在 Protobuf 消息和 JSON 字符串之间转换。

Protobuf 的核心优势在于其高效的二进制格式、强类型定义以及跨语言的兼容性,遵循上述步骤,你就可以在 Java 项目中顺利地使用 Protobuf 进行数据序列化和反序列化了。

分享:
扫描分享到社交APP
上一篇
下一篇