protobuf基礎(java使用,windows代碼生成)


TOC

01protobuf基礎

參考:

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

生成代碼

方法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

    1563242193484

  • 即可在target/generated-sources/protobuf中看到根據.proto文件生成的Java類 ;

    1563242236265

使用

引入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("xxg@163.com");
        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






免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM