Protobuf 是 Google 用于序列化数据对象的一种机制,使得数据对象能够在应用程序和服务器之间进行交互,尽管现在 Java 已经对应的序列化的实现方式,但是传统的序列化方式存在严重的缺陷,因此现在应该避免使用 Java 自带的序列化方式。

传统序列化方式的局限性

按照传统的实现,如果要使得一个对象能够被序列化成为一个二进制数据流,只需要使得定义的类实现 Serializable 接口即可,由于这个接口是一个空接口,因此大多数人认为实现序列化很简单,导致了 Serializable 接口的滥用

《Effective Java》(第三版)中总结了 Serializable 存在的不足[2]

  1. 类如果实现 Serializable 接口会大幅度降低类的灵活性
  2. 实现 Serializable 接口会提高 bug 和漏洞出现的概率
  3. 随着类的发行版本的更新,相关测试的负担也会加重

在书中,作者建议如果必须实现传输二进制数据对象的话,建议使用 JSON 的序列化方式或者是 Protocol Buffer(ProtoBuf)的方式来实现

使用 JSON 的序列化方式比较简单,因为序列化之后的数据对于人类来讲是可见的,Jackson 和 Gson 都能够很好地帮助我们完成这一工作,在此不做过多的介绍

protoc 的安装

ProtoBuf 通过对应的 .proto 文件来生成需要的具体类信息,通过选择对应的参数选项即可生成不同的编程语言的版本,这个转换的过程是通过 protoc 来实现的。你可以认为这个工具是 .proto 源文件的一个编译器,与传统的编译器不同的地方在于别的编译器是将源文件转换为二进制文件,而 protoc 则是将 .proto 文件文件转换成为对应编程语言的源文件

如果是在 Ubuntu 操作系统上,可以通过如下的命令直接安装 protoc

sudo apt-get install protobuf-compiler

如果是别的操作系统,如 Windows,可以直接到官方的 GitHub 的发行版本中直接下载对应的二进制版本,具体地址:https://github.com/protocolbuffers/protobuf/releases/tag/v3.19.1, 然后将 protoc 程序所在的目录添加到 PATH 环境变量即可

编写 proto 文件

以一个简单的 addressBook.proto 文件文件为例,介绍一下有关 .proto 的语法:

// 使用的 proto 语法的版本
syntax="proto2"; // 如果没有指定 java_package,那么将这个包位置作为生成类的目的包
package org.xhliu.proto.entity; // 确保能够为每个类生成独立的 .java 文件而不是单个的 .java 文件
option java_multiple_files = true;
// 生成的目标类所在的包
option java_package = "org.xhliu.proto.entity";
// 定义生成的类的名字
option java_outer_classname = "AddressBookProtos"; // 简单的理解: message 和 class 关键字是对应的
message Person {
/*
对于属性的修饰:
optional 表示这个字段可能被设置属性,也可能不会设置属性
repeated 表示这个字段可能被设置多次,和数组是相对应的
required 表示这个字段必须被设置值,否则将会抛出 RuntimeException
*/
optional string name = 1;
optional int32 id = 2;
optional string email = 3; // 相当于在类中定义了一个枚举类
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
} // 相当于定义了一个内部类
message PhoneNumber {
optional string number = 1;
// type 属性默认为 HOME
optional PhoneType type = 2 [default = HOME];
} repeated PhoneNumber phones = 4;
} // 再定义了一个类,其中包含 Person 类型的属性
message AddressBook {
repeated Person people = 1;
}

具体关于 proto 语言的内容可以参考:https://developers.google.com/protocol-buffers/docs/proto

具体类型和实际变成语言中的类型对应如下[1]

.proto Type Notes C++ Type Java Type Python Type[2] Go Type
double double double float *float64
float float float float *float32
int32 Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have
negative values, use sint32 instead.
int32 int int *int32
int64 Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have
negative values, use sint64 instead.
int64 long int/long[3] *int64
uint32 Uses variable-length encoding. uint32 int[1] int/long[3] *uint32
uint64 Uses variable-length encoding. uint64 long[1] int/long[3] *uint64
sint32 Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular
int32s.
int32 int int *int32
sint64 Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular
int64s.
int64 long int/long[3] *int64
fixed32 Always four bytes. More efficient than uint32 if values are often greater than 228. uint32 int[1] int/long[3] *uint32
fixed64 Always eight bytes. More efficient than uint64 if values are often greater than 256. uint64 long[1] int/long[3] *uint64
sfixed32 Always four bytes. int32 int int *int32
sfixed64 Always eight bytes. int64 long int/long[3] *int64
bool bool boolean bool *bool
string A string must always contain UTF-8 encoded or 7-bit ASCII text. string String unicode (Python 2) or str (Python 3) *string
bytes May contain any arbitrary sequence of bytes. string ByteString bytes []byte

生成具体类

由于对应的项目为 Java 类型的项目,因此需要生成 Java 类型的类,使用示例如下所示:

# -I 表示 .proto 文件所在的路径,--java_out 表示输出 Java 的类,并且指定对应的输出路径
protoc -I=. --java_out=. addressBook.proto

得到的结果如下图所示:

可以看到,已经自动生成了能够使用 ProtoBuf 的类

序列化对象

生成了对应的 ProtoBuf 的类之后,就可以使用这些类来结合 ProtoBuf 来序列化对象了,在那之前,需要添加如下依赖:

<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>${protobuf.version}</version><!-- 这里需要在 Maven 仓库找到对应的版本-->
</dependency>

明确一点,上文中的 .proto 文件只是帮助我们生成了能够使用 ProtoBuf 工具的类文件,实际上和序列化对象没有任何关系,具体的序列化操作是通过这个 protobuf-java 这个依赖项来实现的,请明确这一点

现在,直接使用 .proto 生成的类去调用 ProtoBuf 来实现对象的序列化和反序列化:

  • 序列化

    public static void main(String[] args) throws Exception {
    // 首先,通过构建者模式的方式来构造 Person 对象
    Person person = Person.newBuilder()
    .setId(1)
    .setEmail("123456789@gmail.com")
    .setName("xhliu")
    .addPhones(Person.PhoneNumber.newBuilder().setNumber("123456789").build())
    .build(); // 再通过 AddressBook 的构建者方法来构建 AddressBook 对象
    AddressBook addressBook = AddressBook.newBuilder().addPeople(person).build();
    try (
    FileOutputStream out = new FileOutputStream("/tmp/addressBook.obj");
    ){
    addressBook.writeTo(out);
    System.out.println("序列化 AddressBook 对象成功");
    }
    }

    运行这段代码,可以在 /tmp/ 目录下发现一个名为 addressBook.obj 的二进制文件,这个对象就是通过 ProtoBuf 序列化之后二进制数据,此时使用 hexdump 命令再加上 -C 选项,可以大致查看一下文件的内容,具体如下图所示:

  • 反序列化

    public static void main(String[] args) throws Exception {
    try (
    FileInputStream in = new FileInputStream("/tmp/addressBook.obj")
    ){
    AddressBook obj = AddressBook.parseFrom(in);
    System.out.println("反序列化之后的对象:");
    System.out.println(obj.getPeople(0));
    }
    }

    运行之后的结果如下图所示:

除了学习了 ProtoBuf 之外,我认为在这个过程中更加值得学习的是 Google 对于 ProtoBuf 的使用,通过 .proto 文件来结合使用对应的依赖库,大大减少了人工编写代码的成本,通过特定的小众语言来达到这一目标,这是十分值得学习的(《编程珠矶(续)》中讨论了对于小众语言的使用)

参考:

[1] https://developers.google.com/protocol-buffers/docs/proto#simple

[2] 《Effective Java》(第三版)

Protobuf 的基本使用的更多相关文章

  1. python通过protobuf实现rpc

    由于项目组现在用的rpc是基于google protobuf rpc协议实现的,所以花了点时间了解下protobuf rpc.rpc对于做分布式系统的人来说肯定不陌生,对于rpc不了解的童鞋可以自行g ...

  2. Protobuf使用规范分享

    一.Protobuf 的优点 Protobuf 有如 XML,不过它更小.更快.也更简单.它以高效的二进制方式存储,比 XML 小 3 到 10 倍,快 20 到 100 倍.你可以定义自己的数据结构 ...

  3. java netty socket库和自定义C#socket库利用protobuf进行通信完整实例

    之前的文章讲述了socket通信的一些基本知识,已经本人自定义的C#版本的socket.和java netty 库的二次封装,但是没有真正的发表测试用例. 本文只是为了讲解利用protobuf 进行C ...

  4. 在Wcf中应用ProtoBuf替代默认的序列化器

    Google的ProtoBuf序列化器性能的牛逼已经有目共睹了,可以把它应用到Socket通讯,队列,Wcf中,身为dotnet程序员一边期待着不久后Grpc对dotnet core的支持更期待着Wc ...

  5. protobuf的编译安装

    github地址:https://github.com/google/protobuf支持多种语言,有多个语言的版本,本文采用的是在centos7下编译源码进行安装. github上有详细的安装说明: ...

  6. 编译protobuf的jar文件

    1.准备工作 需要到github上下载相应的文件,地址https://github.com/google/protobuf/releases protobuf有很多不同语言的版本,因为我们需要的是ja ...

  7. protobuf学习(2)-相关学习资料

    protobuf官方git地址 protobuf官方英文文档   (你懂的需要FQ) protobuf中文翻译文档 protobuf概述          (官方翻译 推荐阅读) protobuf入门 ...

  8. google protobuf安装与使用

    google protobuf是一个灵活的.高效的用于序列化数据的协议.相比较XML和JSON格式,protobuf更小.更快.更便捷.google protobuf是跨语言的,并且自带了一个编译器( ...

  9. c# (ENUM)枚举组合类型的谷歌序列化Protobuf

    c# (ENUM)枚举组合类型的谷歌序列化Protobuf,必须在序列化/反序列化时加上下面: RuntimeTypeModel.Default[typeof(Alarm)].EnumPassthru ...

  10. dubbox 增加google-gprc/protobuf支持

    好久没写东西了,今年实在太忙,基本都在搞业务开发,晚上来补一篇,作为今年的收官博客.google-rpc 正式发布以来,受到了不少人的关注,这么知名的rpc框架,不集成到dubbox中有点说不过去. ...

随机推荐

  1. 教育法学第九章单元测试MOOC

    第九章单元测试 返回 本次得分为:100.00/100.00, 本次测试的提交时间为:2020-09-06, 如果你认为本次测试成绩不理想,你可以选择 再做一次 . 1 单选(5分) 作为教师最基本的 ...

  2. js性能优化解决办法

    1. 减少http请求次数:CSS Sprites, JS.CSS 源码压缩.图片大小控制合适:网页 Gzip,CDN 托管,data 缓存 ,图片服务器 2. 前端模板 JS + 数据,减少由于HT ...

  3. 自然数的拆分问题(lgP2404)

    dfs.又调了一个小时,窝果然菜 需要传递的变量分别为目前搜索的数字:目前所有选中数字的和:目前所选数字个数. 见注释. #include<bits/stdc++.h> using nam ...

  4. 阿里发布AI编码助手:通义灵码,兼容 VS Code、IDEA等主流编程工具

    今天是阿里云栖大会的第一天,相信场外的瓜,大家都吃过了.这里就不说了,有兴趣可以看看这里:云栖大会变成相亲现场,最新招婿鄙视链来了... . 这里主要说说阿里还发布了一款AI编码助手,对于我们开发者来 ...

  5. [转载]R2: 已解释和未解释的方差

    估计值的方差与总体方差之间的差异就是回归方程对方差的解释率.试举一例,如图 1,身高与体重的回归线显示身高与体重之间呈正相关,Mr. Y身高76英寸体重220磅(图 1中插图.cdr的红点),他与体重 ...

  6. Markdown使用心得(简单用法解析)

    Markdown使用心得(简单用法解析) Markdown的优势 个人看来,MD的优势在于脱离对鼠标的依赖,在简单的熟悉后,从段落格式到字体特效的实现都可以完全脱离鼠标.避免了为了格式和艺术效果多次将 ...

  7. Flask解决跨域问题

    什么是跨域问题 跨域问题指的是浏览器限制了从一个源(协议.域名.端口)访问另一个源的资源的行为,这个限制是浏览器的一个安全机制.如果一个网页从一个源加载了另一种类型的资源(例如 HTML.CSS.脚本 ...

  8. Mysql中的FOREIGN_KEY_CHECKS方法【外键约束作用】

    一.命令行 首先FOREIGN_KEY_CHECKS方法的作用是用来启动和关闭外键约束的方法. 二.外键约束 即数据库中两个数据表之间的某个列建立的一种联系.MySQL通过外键约束的引入,可以使得数据 ...

  9. Android的内部存储和外部存储怎么区分?

    1.定义 内部存储:内部存储位于Android手机系统的data/data/<包名>这个目录下,内部存储是私有的,主要用于存储系统和应用程序的某些数据,对于其他应用程序来说是不可见的,并且 ...

  10. 利用 Excel 对学生的成绩进行分析管理

    利用 Excel 对学生的成绩进行分析和管理是一种常见且有效的方法.以下是一些步骤和技巧,以帮助您实施这一过程: 1. 数据输入:将学生成绩数据输入到 Excel 中的一个工作表中.每个学生可以有一行 ...