在java程序中使用protobuf

目录
  • 在java程序中使用protobuf
    • 1、为什么使用protobuf
    • 2、定义.proto文件
    • 3、编译协议文件
    • 4、详解生成的文件
    • 5、Builders 和 Messages
    • 6、序列化和反序列化
    • 7、协议扩展

在java程序中使用protobuf

1、为什么使用protobuf

我们知道数据在网络传输中是以二进制进行的,一般我们使用字节byte来表示, 一个byte是8bits,如果要在网络上中传输对象,一般需要将对象序列化,序列化的目的就是将对象转换成byte数组在网络中传输,当接收方接收到byte数组之后,再对byte数组进行反序列化,最终转换成java中的对象。

那么将java对象序列化可能会有如下几种方法:

(1)使用JDK自带的对象序列化,但是JDK自带的序列化本身存在一些问题,并且这种序列化手段只适合在java程序之间进行传输,如果是非java程序,比如PHP或者GO,那么序列化就不通用了。
(2)你还可以自定义序列化协使用JDK自带的对象序列化,但是JDK自带的序列化本身存在一些问题,并且这种序列化手段只适合在java程序之间进行传输,如果是非java程序,比如PHP或者GO,那么序列化就不通用了。
你还可以自定义序列化协议,这种方式的灵活程度比较高,但是不够通用,并且实现起来也比较复杂,很可能出现意想不到的问题。
(3)将数据转换成为XML或者JSON进行传输。XML和JSON的好处在于他们都有可以区分对象的起始符号,通过判断这些符号的位置就可以读取到完整的对象。但是不管是XML还是JSON的缺点都是转换成的数据比较大。在反序列化的时候对资源的消耗也比较多。

所以我们需要一种新的序列化的方法,这就是protobuf,它是一种灵活、高效、自动化的解决方案。

通过编写一个.proto的数据结构定义文件,然后调用protobuf的编译器,就会生成对应的类,该类以高效的二进制格式实现protobuf数据的自动编码和解析。 生成的类为定义文件中的数据字段提供了gettersetter方法,并提供了读写的处理细节。 重要的是,protobuf可以向前兼容,也就是说老的二进制代码也可以使用最新的协议进行读取。

2、定义.proto文件

.proto文件中定义的是你将要序列化的消息对象。我们来一个最基本的student.proto文件,这个文件定义了student这个对象中最基本的属性。

先看一个比较简单的.proto文件:

syntax = "proto3";

package com.flydean;

option java_multiple_files = true;
option java_package = "com.flydean.tutorial.protos";
option java_outer_classname = "StudentListProtos";

message Student {
  optional string name = 1;
  optional int32 id = 2;
  optional string email = 3;

  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
  }

  message PhoneNumber {
    optional string number = 1;
    optional PhoneType type = 2;
  }

  repeated PhoneNumber phones = 4;
}

message StudentList {
  repeated Student student = 1;
}

第一行定义的是protobuf中使用的syntax协议,默认情况下是proto2,因为目前最新的协议是proto3,所以这里我们使用proto3作为例子。

然后我们定义了所在的package,这个package是指编译的时候生成文件的包。这是一个命名空间,虽然我们在后面定义了java_package,但是为了和非java语言中的协议相冲突,所以定义package还是非常有必要的。

然后是三个专门给java程序使用的option。java_multiple_files, java_package, 和 java_outer_classname.

其中java_multiple_files指编译过后java文件的个数,如果是true,那么将会一个java对象一个类,如果是false,那么定义的java对象将会被包含在同一个文件中。

java_package指定生成的类应该使用的Java包名称。 如果没有明确的指定,则会使用之前定义的package的值。

java_outer_classname选项定义将表示此文件的包装类的类名。 如果没有给java_outer_classname赋值,它将通过将文件名转换为大写驼峰来生成。 例如,默认情况下,“student.proto”将使用"Student"作为包装类名称。

接下来的部分是消息的定义,对于简单类型来说可以使用bool, int32, float, double, 和 string来定义字段的类型。

上例中我们还使用了复杂的组合属性,和嵌套类型。还定义了一个枚举类。

上面我们为每个属性值分配了ID,这个ID是二进制编码中使用的唯一“标签”。因为在protobuf中标记数字1-15比16以上的标记数字占用的字节空间要更少,因此作为一种优化,通常将1-15这些标记用于常用或重复的元素,而将标记16和更高的标记用于不太常用的可选元素。

然后再来看看字段的修饰符,有三个修饰符分别是optional,repeated和required。

optional表示该字段是可选的,可以设置也可以不设置,如果没有设置,则会使使用默认值,对于简单类型来说,我们可以自定义默认值,如果不自定义,就会使用系统的默认值。对于系统的默认值来说,数字为0,字符串为空字符串,布尔值为false。

repeated表示该字段是可以重复的,这种重复实际上就是一种数组的结构。

required表示该字段是必须的,如果该字段没有值,那么该字段将会被认为是没有初始化,尝试构建未初始化的消息将抛出 RuntimeException,解析未初始化的消息将抛出 IOException。

注意:在Proto3中不支持required字段。

3、编译协议文件

定义好proto文件之后,就可以使用protoc命令对其进行编译了。

protoc是protobuf提供的编译器,一般情况下,可以从github的release库中直接下载即可。如果你不想直接下载,或者官方提供的库中并没有你需要的版本,则可以使用源代码直接进行编译。

protoc的使用的命令如下:

protoc --experimental_allow_proto3_optional -I=$SRC_DIR --java_out=$DST_DIR
$SRC_DIR/student.proto

如果编译proto3,则需要添加--experimental_allow_proto3_optional选项。

我们运行一下上面的代码。会发现在com.flydean.tutorial.protos包里面生成了5个文件。分别是:

Student.java
StudentList.java
StudentListOrBuilder.java
StudentListProtos.java
StudentOrBuilder.java

其中StudentListOrBuilderStudentOrBuilder是两个接口,StudentStudentList是这两个类的实现。

4、详解生成的文件

在proto文件中,我们主要定义了两个类Student和StudentList, 他们中定义了一个内部类Builder,以Student为例,看下这个两个类的定义:

public final class Student extends
    com.google.protobuf.GeneratedMessageV3 implements
    StudentOrBuilder

  public static final class Builder extends
      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
      com.flydean.tutorial.protos.StudentOrBuilder

可以看到他们实现的接口都是一样的,表示他们可能提供了相同的功能。实际上Builder是对消息的一个封装器,所有对Student的操作都可以由Builder来完成。

对于Student中的字段来说,Student类只有这些字段的get方法,而Builder中同时有get和set方法。

对于Student来说,对于字段的方法有:

// required string name = 1;
public boolean hasName();
public String getName();

// required int32 id = 2;
public boolean hasId();
public int getId();

// optional string email = 3;
public boolean hasEmail();
public String getEmail();

// repeated .tutorial.Person.PhoneNumber phones = 4;
public List<PhoneNumber> getPhonesList();
public int getPhonesCount();
public PhoneNumber getPhones(int index);

对于Builder来说,每个属性多了两个方法:

// required string name = 1;
public boolean hasName();
public java.lang.String getName();
public Builder setName(String value);
public Builder clearName();

// required int32 id = 2;
public boolean hasId();
public int getId();
public Builder setId(int value);
public Builder clearId();

// optional string email = 3;
public boolean hasEmail();
public String getEmail();
public Builder setEmail(String value);
public Builder clearEmail();

// repeated .tutorial.Person.PhoneNumber phones = 4;
public List<PhoneNumber> getPhonesList();
public int getPhonesCount();
public PhoneNumber getPhones(int index);
public Builder setPhones(int index, PhoneNumber value);
public Builder addPhones(PhoneNumber value);
public Builder addAllPhones(Iterable<PhoneNumber> value);
public Builder clearPhones();

多出的两个方法是set和clear方法。clear是清空字段的内容,让其变回初始状态。

我们还定义了一个枚举类PhoneType

  public enum PhoneType
      implements com.google.protobuf.ProtocolMessageEnum

这个类的实现和普通的枚举类没太大区别。

5、Builders 和 Messages

如上一节所示,Message对应的类只有get和has方法,所以它是不可以变的,消息对象一旦被构造,就不能被修改。要构建消息,必须首先构建一个构建器,将要设置的任何字段设置为你选择的值,然后调用构建器的 build()方法。

每次调用Builder的方法都会返回一个新的Builder,当然这个返回的Builder和原来的Builder是同一个,返回Builder只是为了方便进行代码的连写。

下面的代码是如何创建一个Student实例:

Student xiaoming =
Student.newBuilder()
 .setId(1234)
 .setName("小明")
 .setEmail("flydean@163.com")
.addPhones(
Student.PhoneNumber.newBuilder()
 .setNumber("010-1234567")
.setType(Student.PhoneType.HOME))
 .build();

Student中提供了一些常用的方法,如isInitialized()检测是否所有必须的字段都设置完毕。toString()将对象转换成为字符串。使用它的Builder还可以调用clear()用来清除已设置的状态,mergeFrom(Message other)用来对对象进行合并。

6、序列化和反序列化

生成的对象中提供了序列化和反序列化方法,我们只需要在需要的时候对其进行调用即可:

  • byte[] toByteArray();: 序列化消息并返回一个包含其原始字节的字节数组。
  • static Person parseFrom(byte[] data);: 从给定的字节数组中解析一条消息。
  • void writeTo(OutputStream output);: 序列化消息并将其写入 OutputStream.
  • static Person parseFrom(InputStream input);: 从一个消息中读取并解析消息 InputStream.

通过使用上面的方法,可以很方便的将对象进行序列化和反序列化。

7、协议扩展

我们在定义好proto之后,假如后续还希望对其进行修改,那么我们希望新的协议对历史数据是兼容的。那么我们需要考虑下面几点:

  1. 不能更改现有字段的ID编号。
  2. 不能添加和删除任何必填字段。
  3. 可以 删除可选或重复的字段。
  4. 可以 添加新的可选字段或重复字段,但您必须使用新的ID编号。

到此这篇关于在java程序中使用protobuf的文章就介绍到这了,更多相关在java使用protobuf内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java协程编程之Loom

    目录 Java协程编程Loom 1.Loom项目简单介绍 2.Virtual Thread使用 Java协程编程Loom 前提: OpenJDK给出的官网https://openjdk.java.net/projects/loom有少量Loom项目相关的信息)JDK17编译和JDK18编译等早期版本,Loom早期版本JDK18编译的版本:下载入口在:https://jdk.java.net/loom 由于该JDK版本过高,目前可以使用主流IDE导入Loom-JDK-18+9进行代码高亮和语法提醒

  • Java协程编程之Loom项目实战记录

    目录 前提 Loom项目简单介绍 Virtual Thread使用 小结 前提 之前很长一段时间关注JDK协程库的开发进度,但是前一段时间比较忙很少去查看OpenJDK官网的内容.Java协程项目Loom(因为项目还在开发阶段,OpenJDK给出的官网https://openjdk.java.net/projects/loom中只有少量Loom项目相关的信息)已经在2018年之前立项,目前已经发布过基于JDK17编译和JDK18编译等早期版本,笔者在下载Loom早期版本的时候只找到JDK18编译

  • Java实现冒泡排序

    问题描述 利用冒泡排序把一列数组按从小到大或从大到小排序 (一).冒泡排序思想 以从小到大为例: 1.第一轮的冒泡,从第一个数开始,与相邻的第二个数相比,若第一个数更大,则交换一二的位置,反之不动,结束后进行二三位置两个数的比较,同理如此反复,直到把最大的一个数排到最后一个位置. 2.进行第二轮的冒泡,依旧从第一个数开始,依次比较当前的一二.二三······位置的数,直到把第二大的数排到倒数第二位. 3.如此循环进行,直到所有数按从小到大排列. (二).问题分析 1.输入数组 根据用户输入的进行

  • Java实现选择排序

    问题描述 利用选择排序把一列数组按从小到大或从大到小排序 (一).选择排序思想 以从小到大为例: 1.第一轮选择,从第一个数开始,依次比较后面所有的数,不断选出当前最小的数,直到选出这一轮最小的数,放到第一位 2.第二轮选择,从第二个数开始,依次比较后面所有的数,不断选出当前最小的数,直到选出这一轮最小的数,放到第二位 3.如此循环,直到所有数从小到大排列 (二).问题分析 1. 输入数组 根据用户输入的进行排序的数字数量n,建立一个长度为n的数组 public static void main

  • 布隆过滤器(Bloom Filter)的Java实现方法

    布隆过滤器原理很简单:就是把一个字符串哈希成一个整数key,然后选取一个很长的比特序列,开始都是0,在key把此位置的0变为1:下次进来一个字符串,哈希之后的值key,如果在此比特位上的值也是1,那么就说明这个字符串存在了. 如果按照上面的做法,那就和哈希算法没有什么区别了,哈希算法还有重复的呢. 布隆过滤器是将一个字符串哈希成多个key,我还是按照书上的说吧. 先建立一个16亿二进制常量,然后将这16亿个二进制位全部置0.对于每个字符串,用8个不同的随机产生器(F1,F2,.....,F8)产

  • 在java程序中使用protobuf

    目录 在java程序中使用protobuf 1.为什么使用protobuf 2.定义.proto文件 3.编译协议文件 4.详解生成的文件 5.Builders 和 Messages 6.序列化和反序列化 7.协议扩展 在java程序中使用protobuf 1.为什么使用protobuf 我们知道数据在网络传输中是以二进制进行的,一般我们使用字节byte来表示, 一个byte是8bits,如果要在网络上中传输对象,一般需要将对象序列化,序列化的目的就是将对象转换成byte数组在网络中传输,当接收

  • java程序中protobuf的基本用法示例

    目录 简介 为什么使用protobuf 定义.proto文件 编译协议文件 详解生成的文件 Builders 和 Messages 序列化和反序列化 协议扩展 总结 简介 Protocol Buffer是google出品的一种对象序列化的方式,它的体积小传输快,深得大家的喜爱.protobuf是一种平台无关和语言无关的协议,通过protobuf的定义文件,可以轻松的将其转换成多种语言的实现,非常方便. 今天将会给大家介绍一下,protobuf的基本使用和同java结合的具体案例. 为什么使用pr

  • 如何在Java程序中访问mysql数据库中的数据并进行简单的操作

    在上篇文章给大家介绍了Myeclipse连接mysql数据库的方法,通过本文给大家介绍如何在Java程序中访问mysql数据库中的数据并进行简单的操作,具体详情请看下文. 创建一个javaProject,并输入如下java代码: package link; import java.sql.*; /** * 使用JDBC连接数据库MySQL的过程 * DataBase:fuck, table:person: * 使用myeclipse对mysql数据库进行增删改查的基本操作. */ public

  • Java程序中实现调用Python脚本的方法详解

    本文实例讲述了Java程序中实现调用Python脚本的方法.分享给大家供大家参考,具体如下: 在程序开发中,有时候需要Java程序中调用相关Python脚本,以下内容记录了先关步骤和可能出现问题的解决办法. 1.在Eclipse中新建Maven工程: 2.pom.xml文件中添加如下依赖包之后update maven工程: <dependency> <groupId>org.python</groupId> <artifactId>jython</ar

  • 详解在Java程序中运用Redis缓存对象的方法

    这段时间一直有人问如何在Redis中缓存Java中的List 集合数据,其实很简单,常用的方式有两种: 1. 利用序列化,把对象序列化成二进制格式,Redis 提供了 相关API方法存储二进制,取数据时再反序列化回来,转换成对象. 2. 利用 Json与java对象之间可以相互转换的方式进行存值和取值. 正面针对这两种方法,特意写了一个工具类,来实现数据的存取功能. 1. 首页在Spring框架中配置 JedisPool 连接池对象,此对象可以创建 Redis的连接 Jedis对象.当然,必须导

  • Java程序中Doc文档注释示例教程

    目录 Doc注释规范 @符号的用处 如何生成Doc文档 第一个:Dos命令生成 第二个:IDE工具生成 许多人写代码时总不喜欢写注释,每个程序员如此,嘿嘿,我也一样 不过,话说回来,该写还是要写哦!没人会喜欢一个不写注释的程序员,当然,也没有一个喜欢写注释的程序员,今天,我们就来说说Java注释之一--Doc注释 我们知道,Java支持 3 种注释,分别是单行注释.多行注释和文档注释,我们来看看他们的样子 //单行注释   /* 多行注释 */   /** *@... *.... *文档注释 *

  • Java程序中方法的用法重载和递归

    目录 前言 一.方法的基本用法 1.什么是方法 2.方法的基本定义 3.方法调用的执行过程 4.方法中的实参和形参 二.方法的重载 1.重载的概念 2.例题分析 三.方法的递归 1.递归定义 2.例题分析 3.递归应用场景 总结 前言 "方法"又被称为"函数",其他的编程语言也有这个概念,重要性不言而喻,在这个文章,将介绍如何定义和使用方法. 一.方法的基本用法 1.什么是方法 在Java中,方法定义在类中,它和类的成员属性(数据成员)一起构建一个完整的类.构成方法

  • 如何定位java程序中占用cpu最高的线程堆栈信息

    找出占用cpu最高的线程堆栈信息 在java编码中,有时会因为粗心导致cpu占用较高的情况,为了避免影响程序的正常运行,需要找到问题并解决.这里模拟一个cpu占用较高的场景,并尝试定位到代码行. 示例代码如下: public class Math { public static final int initData = 666; public static User user = new User(); public int compute() { //一个方法对应一块栈帧内存区域 int a

  • 浅析java程序中hibernate的应用总结

    我们知道如果用java连接数据库,大量的SQL代码,我们就想通过一种工具来操作数据库,我们首先想到的就是JDBC,但是想更好的针对类的属性进行操作,我们会选择Hibernate. 那么Hibernate 原理是什么呢?hibernate可以理解为是一个中间件它负责把java程序的sql语句接收过来发送到数据库,而数据库返回来的信息hibernate接收之后直接生成一个对象传给java. 一  首先我们先了解一下hibernate的工作7个流程:1.读取并解析配置文件   2.创建SessionF

  • Java程序中添加播放MIDI音乐功能的实现方法详解

    JAVA 在多媒体处理方面的确优势不大,但是我们在程序中有些时候又需要一些音乐,如果播放的音乐是wav等波形音频文件,又很大的话,所以背景音乐最好就是MIDI了,可是网上很多播放MIDI的教程都是简单的几句话的例子.没有考虑资源的释放问题,如果程序长久运行的话,就会出现内存越耗越多的情况,最后会抛出一个 java.lang.OutOfMemoryError..在MIDI的播放中,一个类是比较重要的,那就是 MidiSystem 类,负责整个MIDI播放设备等的管理,其实就是 Seqencer,它

随机推荐