protobuf入门
为什么用protobuf
怎么序列化和反序列化一个数据结构
- java的自带序列化和反序列化,但是不能在不同的编程语言系统之间进行传输
- 可以用简单的string表示,比如“name:csdn”,但是不能描述复杂的数据结构
- 也可以使用xml,xml为很多语言提供了工具,不需要重新开发;但是xml读取一个属性需要遍历整个dom树,这会耗费大量性能,以至于使读取属性变得复杂。
protobuf 可以很好的解决上述问题,它需要定义.proto文件来描述数据结构
.proto 文件 定义
在src/main/proto (默认路径) 新建addressbook.proto 文件
syntax = "proto2";
//syntax 版本,不写默认proto2,目前有proto2 和proto3,必须位于第一行,不能有注释
//包名,和java的包名一样为了防止类的name冲突,如果你明确指定了包名,也建议放在一个正常的java包里,防止在不同系统间 类名冲突
package tutorial;
//指定了自动生成的类放在哪个包里,如果没有指定将会用 上面的package
option java_package = "com.example.tutorial";
//如果不指定默认叫做 MyProto.java
option java_outer_classname = "AddressBookProtos";
//message可以嵌套,像java的内部类
message Person {
// ‘=1’ 是在二进制文件中的唯一标识,0-15比16以上的少一个字节,在同一个message中相同字段名可以用同一个标识
//required 表示这个字段必须被赋值,否则将不能进行初始化,没有赋值初始化会报RuntimeException, 反序列化会报IOException,其它的和option一样,慎用requeried, 如果一个字段需要变成option,则所有的序列化和反序列都需要变化 注意:在proto3版本中 required,optional 已取消
required string name = 1;
required int32 id = 2;
//option 可以不被赋值,不被赋值将会使用默认值,数据的默认值是0,boolean的是false,string的是空字符串,嵌入message的默认值是 "default instance" 或者"prototype"
optional string email = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
required string number = 1;
optional PhoneType type = 2 [default = HOME];
}
//可以被使用多次,在序列化时会被当成一个动态数组
repeated PhoneNumber phones = 4;
}
message AddressBook {
repeated Person people = 1;
}
.proto转换为java bean
使用命令转换
下载系统对应的版本(window可以不安装)https://github.com/protocolbuffers/protobuf/releases/tag/v3.9.1
C:\Users\46133\Downloads\protoc-3.9.1-win64\bin\protoc.exe -I=E:\idea_study\src\main\proto\ --java_out=E:\idea_study\src\main\ E:\idea_study\src\main\proto\addressbook.proto
C:\Users\46133\Downloads\protoc-3.9.1-win64\bin\protoc.exe 下载解压的文件
-I=E:\idea_study\src\main\proto\ proto文件所在的文件夹
–java_out=E:\idea_study\src\main\ 输出java 类文件到哪个目录
E:\idea_study\src\main\proto\addressbook.proto 编辑哪个proto的文件
使用idea插件转换
- idea 安装Protobuf Support 插件链接
注意:把.proto文件转换为javaBean时 ,.proto文件的默认包名是src/main/proto - 添加maven依赖
<properties>
<grpc.version>1.6.1</grpc.version>
<protobuf.version>3.3.0</protobuf.version>
</properties>
<dependencies>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty</artifactId>
<version>${grpc.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>${grpc.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>${grpc.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>${protobuf.version}</version>
</dependency>
</dependencies>
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.5.0.Final</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.5.0</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}
</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}
</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
-
idea编译一下项目(或 mvn compile 命令),会把默认路径 src/main/proto下面的.proto文件生成java 类
-
生成的java 包、类名对应 addressbook.proto 中的
option java_package = "com.example.tutorial"; //包名
option java_outer_classname = "AddressBookProtos"; //类名
- 生成的类有对应的get 和set 方法,也有builder,一些常用的api
AddressBookProtos.Person john =
AddressBookProtos.Person.newBuilder()
.setId(1234)
.setName("John Doe")
.setEmail("jdoe@example.com")
.addPhones(
AddressBookProtos.Person.PhoneNumber.newBuilder()
.setNumber("555-4321")
.setType(AddressBookProtos.Person.PhoneType.HOME))
.build();
System.out.println(john.getPhonesCount());
api
-
标准api
-
isInitialized() 查看所有的required 字段是否被赋值
-
toString() 转换成可读性比较友好的字符串,debug时可以用
-
mergeFrom(Message other) 合并另一个message,如果是字段非集合就覆盖,集合就合并,repeated就关联,(只有在builder中才能使用)
-
clear() 清空该字段
-
序列化API
-
byte[] toByteArray(); 转换为byte[]
-
static Person parseFrom(byte[] data); byte[] 转换为对象
-
void writeTo(OutputStream output) 序列化对象到输出流
-
static Person parseFrom(InputStream input) 从输入流读取对象
-
输入到文件
AddressBookProtos.Person john =
AddressBookProtos.Person.newBuilder()
.setId(1234)
.setName("John Doe")
.setEmail("jdoe@example.com")
.addPhones(
AddressBookProtos.Person.PhoneNumber.newBuilder()
.setNumber("555-4321")
.setType(AddressBookProtos.Person.PhoneType.HOME))
.build();
String filePath = System.getProperty("user.dir") + File.separator + "protobuf";
FileOutputStream output = new FileOutputStream(filePath);
john.writeTo(output);
output.close();
System.out.println("输出文件流到" + filePath);
- 从文件读取 (读取上面输出到文件的)
String filePath = System.getProperty("user.dir") + File.separator + "protobuf";
FileInputStream inputStream = new FileInputStream(filePath);
Person person = Person.newBuilder().mergeFrom(inputStream).build();
System.out.println(person.toString());
probobuf扩展
有一天你的类需要进行修改,调用方不修改类的情况下,一定要遵守以下几条
- 不能改变已有字段 的tag number
- 不能增加和删除 required 字段
- 可以删除optional or repeated fields.
- 可以增加optional or repeated fields ,但是必须使用新的tag number,(从来没有使用过的,包括删除的字段,建议删除字段不要删除,注释掉)