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)
