当前位置 : 主页 > 大数据 > 区块链 >

protobuf基础(java使用,windows代码生成)

来源:互联网 收集:自由互联 发布时间:2021-06-22
01protobuf基础 protobuf概述 message 定义message结构 保留Filed和保留Filed number 枚举类型 引用其它message类 message扩展 数据类型对应关系 编码规则 可变长整数编码 有符号整数编码 定长编码 代码

01protobuf基础
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中的数据类型对应规则如下:

.proto C++ Python java 介绍 double double float double float float float float int32 int32 int int 使用可变长编码方式。编码负数时不够高效——如果你的字段可能含有负数,那么请使用sint32。 int64 int64 int/long long 使用可变长编码方式。编码负数时不够高效——如果你的字段可能含有负数,那么请使用sint64。 uint32 uint32 int/long int Uses variable-length encoding. uint64 uint64 int/long long Uses variable-length encoding. sint32 int32 int int 使用可变长编码方式。有符号的整型值。编码时比通常的int32高效。 sint64 int64 int/long long 使用可变长编码方式。有符号的整型值。编码时比通常的int64高效。 fixed32 uint32 int/long int 总是4个字节。如果数值总是比总是比228大的话,这个类型会比uint32高效。 fixed64 uint64 int/long long 总是8个字节。如果数值总是比总是比256大的话,这个类型会比uint64高效。. sfixed32 int32 int int 总是4个字节。 sfixed64 int64 int/long long 总是8个字节。 bool bool bool boolean string string str/unicode String 一个字符串必须是UTF-8编码或者7-bit ASCII编码的文本。 bytes string str ByteString 可能包含任意顺序的字节数据。

编码规则

protobuf有一套高效的数据编码规则。

可变长整数编码

  每个字节有8bits,其中第一个bit是most significant bit(msb),0表示结束,1表示还要读接下来的字节。

  对message中每个Filed来说,需要编码它的数据类型、对应id以及具体数据。

  数据类型有以下6种,可以用3个bits表示。每个整数编码用最后3个bits表示数据类型。所以,对应id在1~15之间的Filed,可以用1个字节编码数据类型、对应id。

Type Meaning Used For 0 Varint int32, int64, uint32, uint64, sint32, sint64, bool, enum 1 64-bit fixed64, sfixed64, double 2 Length-delimited string, bytes, embedded messages, packed repeated fields 3 Start group groups (deprecated) 4 End group groups (deprecated) 5 32-bit fixed32, sfixed32, float

比如对于下面这个例子来说,如果给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 projectsPlugins--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)
上一篇:每日一题_190929
下一篇:每日一题_190927
网友评论