protobuf概述
message
定义message结构
保留Filed和保留Filed number
枚举类型
引用其它message类
message扩展
数据类型对应关系
编码规则
可变长整数编码
有符号整数编码
定长编码
代码生成
下载安装protobuf
生成代码
方法1:使用cmd
方法2:使用java调用cmd
方法3:使用pom生成java类
使用
引入protobuf
使用builder
idea使用protobuf
添加protobuf支持 TOC
01protobuf基础
参考:
- https://www.jianshu.com/p/419efe983cb2
- https://blog.csdn.net/Erica_1230/article/details/78746757
- https://blog.csdn.net/q2365921/article/details/52672346
- https://blog.csdn.net/antgan/article/details/52103966
- https://www.cnblogs.com/liugh/p/7505533.html
- https://blog.csdn.net/cb2474600377/article/details/49511873
protobuf概述
protobuf是google团队开发的用于高效存储和读取结构化数据的工具。什么是结构化数据呢,正如字面上表达的,就是带有一定结构的数据。比如电话簿上有很多记录数据,每条记录包含姓名、ID、邮件、电话等,这种结构重复出现。
xml、json也可以用来存储此类结构化数据,但是使用protobuf表示的数据能更加高效,并且将数据压缩得更小,大约是json格式的1/10,xml格式的1/20。
它是一种轻便高效的数据格式,平台无关、语言无关、可扩展,可用于通讯协议和数据存储等领域。
- 优点
- 平台无关,语言无关,可扩展;
- 提供了友好的动态库,使用简单;
- 解析速度快,比对应的XML快约20-100倍;
- 序列化数据非常简洁、紧凑,与XML相比,其序列化之后的数据量约为1/3到1/10。
- 缺点
- 不适合用于对基于文本的标记文档(如HTML)建模,因为文本不适合描述数据结构
- 通用性较差:Json, XML已经成为多种行业标准的编写工具,而Protobuf只是Google公司内部使用的工具
- 自解释性差:以二进制数据流方式存储(不可读) ,需要通过.proto文件才能了解到数据结构
message
定义message结构
protobuf使用message,类似class文件,
例如:
message Person { // ID(必需) required int32 id = 1;[default = 0] // 姓名(必需) required string name = 2; // email(可选) optional string email = 3;[default = ""] // 朋友(集合) repeated string friends = 4 [packed=true]; }
其中Person是message
这种结构的名称,name、id、email是其中的Field,每个Field保存着一种数据类型,后面的1、2、3是Filed对应的数字id。id在1~15之间编码只需要占一个字节,包括Filed数据类型和Filed对应数字id,在16~2047之间编码需要占两个字节,所以最常用的数据对应id要尽量小一些。
optional
后面可以加default默认值,如果不加,数据类型的默认为0[default = 0],字符串类型的默认为空串[default = ""]。
repeated
后面加[packed=true]会使用新的更高效的编码方式。
注意:使用required规则的时候要谨慎,因为以后结构若发生更改,这个Filed若被删除的话将可能导致兼容性的问题。
syntax = "proto3"; package net.cc.luffy.entity.proto;//指定java的包名,生成java之后的包路径 //option java_package = "net.cc.luffy.entity.proto"; \\指定java的报名 option java_outer_classname = "UpDownProto";//指定java的编译前类名,生成java之后,java文件交 // 起降记录 message UpDown { // 起降记录ID fixed64 id = 1; // 设备ID string deviceId = 2; // 用户ID fixed64 usrId = 3; // 厂商ID string mid = 4; // 起飞时间 fixed64 upTime = 5; // 降落时间 fixed64 downTime = 6; // 飞行状态 int32 flyStatus = 7; // 是否删除 bool isDelete = 8; // 日志跟踪ID string traceId = 9; // 创建时间 fixed64 createdate = 10; // 平均速度 double avgSpeed = 11; // 平均高度 double avgHeight = 12; // 最大速度 double maxSpeed = 13; // 最大高度 double maxHeight = 14; // 最小速度 double minSpeed = 15; // 最小高度 double minHeight = 16; // 开关机记录ID fixed64 onOffId = 17; }
保留Filed和保留Filed number
每个Filed对应唯一的数字id,但是如果该结构在之后的版本中某个Filed删除了,为了保持向前兼容性,需要将一些id或名称设置为保留的,即不能被用来定义新的Field。
实质:已经定义的数字id和字段为了兼容性,之后不能重复使用,若是对应的字段删除,就记录下来,以后不再使用
message Person { reserved 2, 15, 9 to 11;//排除的id reserved "samples", "email";//排除的字段 }
枚举类型
比如电话号码,只有移动电话、家庭电话、工作电话三种,因此枚举作为选项,如果没设置的话枚举类型的默认值为第一项。
在上面的例子中在个人message中加入电话号码这个Filed。如果枚举类型中有不同的名字对应相同的数字id,需要加入option allow_alias = true
这一项,否则会报错。枚举类型中也有reserverd Filed和number,定义和message中一样。
message Person { //普通参数 required string name = 1; required int32 id = 2; optional string email = 3; //枚举--手机号类型(移动电话、家庭电话、工作电话) enum PhoneType { //allow_alias = true; MOBILE = 0; HOME = 1; WORK = 2; } //定义手机号信息 message PhoneNumber { required string number = 1; optional PhoneType type = 2 [default = HOME];//默认是家庭电话 } //手机号字段 repeated PhoneNumber phones = 4; }
引用其它message类
- 在同一个文件中,可以直接引用定义过的message类型。
- 在一个message类型中嵌套定义其它的message类型
- 在同一个项目中,可以用
import
来导入其它message类型。
import "myproject/other_protos.proto";
message扩展
message Person { // ... extensions 100 to 199;//扩展允许的id范围 }
在另一个文件中,import 这个proto之后,可以对Person这个message进行扩展。
extend Person { optional int32 bar = 126; }
数据类型对应关系
在使用规则创建proto类型的数据结构文件之后,会将其转化成对应编程语言中的头文件或者类定义。
proto中的数据类型和c++,Python中的数据类型对应规则如下:
编码规则
protobuf有一套高效的数据编码规则。
可变长整数编码
每个字节有8bits,其中第一个bit是most significant bit(msb),0表示结束,1表示还要读接下来的字节。
对message中每个Filed来说,需要编码它的数据类型、对应id以及具体数据。
数据类型有以下6种,可以用3个bits表示。每个整数编码用最后3个bits表示数据类型。所以,对应id在1~15之间的Filed,可以用1个字节编码数据类型、对应id。
比如对于下面这个例子来说,如果给a赋值150,那么最终得到的编码是什么呢?
message Test { optional int32 a = 1; }
首先数据类型编码是000,因此和id联合起来的编码是00001000. 然后值150的编码是1 0010110,采用小端序交换位置,即0010110 0000001,前面补1后面补0,即10010110 00000001,即96 01,加上最前面的数据类型编码字节,总的编码为08 96 01。
有符号整数编码
如果用int32来保存一个负数,结果总是有10个字节长度,被看做是一个非常大的无符号整数。使用有符号类型会更高效。它使用一种ZigZag
的方式进行编码。即-1编码成1,1编码成2,-2编码成3这种形式。
也就是说,对于sint32来说,n编码成 (n << 1) ^ (n >> 31),注意到第二个移位是算法移位。
定长编码
定长编码是比较简单的情况。
代码生成
下载安装protobuf
- 在https://github.com/protocolbuffers/protobuf/releases 下载合适的版本
例如,我下载了protoc-3.9.0-win32.zip - 将解压出来的protoc.exe放在一全英文路径下,并把其路径名放在windows环境变量下的
path
下,同时添加proto_path
,值为protoc.exe的路径
生成代码
方法1:使用cmd
在所使用的proto文件路径下打开cmd窗口执行以下命令
?```java
protoc -I=源地址 --java_out=目标地址 源地址/xxx.proto
注:此处生成时会以proto里面注明的java_package为路径完整生成,所以目标地址不必包含java_package及之后的路径,比如: `option java_package = "com.test.protocol"; `那么就会生成`com/test/protocol/XXX.java` - -I选项,主要用于指定待编译的.proto消息定义文件所在的目录,即可能出现的包含文件的路径,该选项可以被同时指定多个。此处指定的路径不能为空,如果是当前目录,直接使用.,如果是子目录,直接使用子目录相对径,如:foo/bar/baz,如果要编译的文件import指定的文件路径为baz/test.proto,那么应这么写-I=foo/bar,而不要一直写到baz。 示例命令如下: ```java protoc -I=. --java_out=../../../../ beans/*.proto apis/*.proto *.proto
方法2:使用java调用cmd
/** * protoc.exe * @author ganhaibin * */ public class GenerateClass { public static void main(String[] args) { String protoFile = "person-entity.proto";// String strCmd = "d:/dev/protobuf-master/src/protoc.exe -I=./proto --java_out=./src/main/java ./proto/"+ protoFile; try { Runtime.getRuntime().exec(strCmd); } catch (IOException e) { e.printStackTrace(); }//通过执行cmd命令调用protoc.exe程序 } }
方法3:使用pom生成java类
<!--版本--> <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配置--> <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>
- 在
src/main/proto
文件夹中添加protobuf的文件; - 右键
proto
,将文件夹设置为源码目录 - 点击
maven projects
中Plugins
--protobuf
--protobuf:compile
- 即可在
target/generated-sources/protobuf
中看到根据.proto文件生成的Java类 ;
使用
引入protobuf
- maven项目,引入pom依赖
<dependency> <groupId>com.google.protobuf</groupId> <artifactId>protobuf-java</artifactId> <version>2.5.0</version> </dependency>
- 普通项目需要引入protobuf-java-2.5.0.jar文件
使用builder
import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.List; public class Main { public static void main(String[] args) throws IOException { // 按照定义的数据结构,创建一个Person PersonMsg.Person.Builder personBuilder = PersonMsg.Person.newBuilder(); personBuilder.setId(1); personBuilder.setName("叉叉哥"); personBuilder.setEmail("[email protected]"); personBuilder.addFriends("Friend A"); personBuilder.addFriends("Friend B"); PersonMsg.Person xxg = personBuilder.build(); // 将数据写到输出流,如网络输出流,这里就用ByteArrayOutputStream来代替 ByteArrayOutputStream output = new ByteArrayOutputStream(); xxg.writeTo(output); // -------------- 分割线:上面是发送方,将数据序列化后发送 --------------- byte[] byteArray = output.toByteArray(); // -------------- 分割线:下面是接收方,将数据接收后反序列化 --------------- // 接收到流并读取,如网络输入流,这里用ByteArrayInputStream来代替 ByteArrayInputStream input = new ByteArrayInputStream(byteArray); // 反序列化 PersonMsg.Person xxg2 = PersonMsg.Person.parseFrom(input); System.out.println("ID:" + xxg2.getId()); System.out.println("name:" + xxg2.getName()); System.out.println("email:" + xxg2.getEmail()); System.out.println("friend:"); List<String> friends = xxg2.getFriendsList(); for(String friend : friends) { System.out.println(friend); } } }
也可以: 序列化:byte[] bytes=personBuilder.build().toByteArray(); 反序列化:PersonMsg.Person person=PersonMsg.Person.parseFrom(bytes); 可以直接 person.getId();等操作
注意:protobuf不是json,若是使用json解析会抛出异常
idea使用protobuf
添加protobuf支持
安装插件Protobuf Support
来自为知笔记(Wiz)