Protobuf初探

Java大猿帥成長手冊, GitHub JavaEgg ,N線互聯網開發必備技能兵器譜

Google Protocol Buffer( 簡稱 Protobuf) 是 Google 公司內部的混合語言數據標準 ,是一種輕便高效的結構化數據存儲格式,能夠用於結構化數據串行化,或者說序列化(將 數據結構或對象 轉換成 二進制串 的過程 )。它很適合作數據存儲RPC 數據交換格式。可用於通信協議、數據存儲等領域的語言無關、平臺無關、可擴展的序列化結構數據格式 java

protocol buffers 誕生之初是爲了解決服務器端新舊協議(高低版本)兼容性問題,名字也很體貼,「協議緩衝區」。只不事後期慢慢發展成用於傳輸數據。git

筆者所在的360廣告投放,N億條商品信息的數據所有采用PB格式存儲、傳輸。github

Protobuf 的優勢

  • 更小——序列化後,數據大小可縮小約3倍
  • 更快——序列化速度更快,比xml和JSON快20-100倍,體積縮小後,傳輸時,帶寬也會優化
  • 更簡單——proto編譯器,自動進行序列化和反序列化
  • 維護成本低——跨平臺、跨語言,多平臺僅須要維護一套對象協議(.proto)
  • 可擴展——「向後」兼容性好,沒必要破壞已部署的、依靠「老」數據格式的程序就能夠對數據結構進行升級
  • 加密性好——HTTP傳輸內容抓包只能看到字節

    在傳輸數據量大、網絡環境不穩定的數據存儲和RPC數據交換場景比較合適數組

Protobuf 的不足

  • 功能簡單,沒法用來表示複雜的概念
  • 通用性較差,XML和JSON已成爲多種行業標準的編寫工具,pb只是geogle內部使用
  • 自解釋性差,以二進制數據流方式存儲(不可讀),須要經過.proto文件才能夠

官網 Protocol Buffer Basics: Java https://developers.google.com...服務器

Hello World

1. 定義 .proto 文件的消息格式(你但願存儲的數據格式描述文件)
syntax = "proto2";

package tutorial;

option java_package = "com.example.tutorial";
option java_outer_classname = "AddressBookProtos";

//消息模型
message Person {
//消息對象的字段:字段修飾符+字段類型+字段名稱+標識號(經過二進制格式惟一標識每一個字段,不變可)
  required string name = 1;
  required int32 id = 2;
  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;
}

☆☆☆注:網絡

  • syntax = "proto2":指明版本
  • package:PB的本身的包名,防止不一樣 .proto 項目間命名 發生衝突
  • java_package: 生成java類的包名,如不顯式指定,默認包名爲:按照應用名稱倒序方式進行排序
  • java_outer_classname:生成 java類的類名,如不顯式指定,則默認爲把.proto文件名轉換爲首字母大寫來生成
  • message: 你的消息格式,各數據類型(boolint32floatdouble,  stringenum ... )字段的集合,在一個.proto文件中能夠定義多個message,一個message裏也能夠定義另一個message(至關於java的類,固然也能夠有內部類)
  • 固然PB也是支持和java同樣的import的,import "xxx.proto";
  • 像每一個字段也必須有修飾符,PB提供的字段修飾符有3種數據結構

    • required:必填
    • optional:可選
    • repeated :可重複字段,可放集合
  • 標識號:經過二進制格式惟一標識每一個字段 ,使用後就不可以再改變ide

    • 標識號使用範圍:[1,2的29次方 - 1]
    • 不可以使用 [19000-19999] 標識號, 由於 Protobuf 協議實現中對這些標識號進行了預留。倘若使用,則會報錯
    • 每一個字段在進行編碼時都會佔用內存,而 佔用內存大小 取決於 標識號:工具

      • 範圍 [1,15] 標識號的字段 在編碼時佔用1個字節;
      • 範圍 [16,2047] 標識號的字段 在編碼時佔用2個字節
      • 爲頻繁出現的 消息字段 保留 [1,15] 的標識號

2. 使用 protocol buffer 編譯器(下載地址:https://github.com/protocolbu... )
winows的話 cmd到編譯器安裝目錄的bin目錄中,執行 protoc.exe -h (E:learningprotoc-3.9.0-win64bin>protoc.exe -h),能夠看到參數說明。

執行:protoc -I=源地址 --java_out=目標地址  源地址/xxx.proto 優化

E:learningprotoc-3.9.0-win64bin>protoc.exe -I=E:learning --java_out=E:lear
ning E:learningaddressbook.proto

實際使用中:

protoc.exe -I=E:learn-workspacestarfishstarfish-learnsrcmainjavaprivstarfishProtocolBuffersproto --java_out=E:learn-workspacestarfishstarfish-learnsrcmainjava E:learn-workspacestarfishstarfish-learnsrcmainjavaprivstarfishProtocolBuffersprotoaddressbook.proto)

pd-idea-screenshot

3. 經過 Java protocol buffer API 讀寫消息格式
package priv.starfish.ProtocolBuffers;
import com.google.protobuf.InvalidProtocolBufferException;
import priv.starfish.ProtocolBuffers.AddressBookProtos.Person;
import priv.starfish.ProtocolBuffers.AddressBookProtos.AddressBook;
import java.util.Arrays;
/**
 * @author: starfish
 * @date: 2019/7/24 14:39
 * @description:
 */
public class HelloProto {
    public static void main(String[] args) {
        Person person = Person.newBuilder()
                .setId(123)
                .setName("starfish")
                .setEmail("starfish@126.cn")
                .addPhones(AddressBookProtos.Person.PhoneNumber.newBuilder()
                        .setType(AddressBookProtos.Person.PhoneType.HOME)
                        .setNumber("13555555555")
                        .build())
                .build();

        System.out.println(person.toString());
        System.out.println(person.isInitialized());

        try {
            //序列化和反序列化
            System.out.println(Arrays.toString(person.toByteArray()));
            System.out.println(person.toByteString());
            Person newPerson = Person.parseFrom(person.toByteArray());
            System.out.println(newPerson);
            newPerson = Person.parseFrom(person.toByteString());
            System.out.println(newPerson);
        } catch (InvalidProtocolBufferException e) {
            e.printStackTrace();
        }

        // 向地址簿添加兩條Person信息
        AddressBook.Builder books = AddressBook.newBuilder();
        books.addPeople(person);
        books.addPeople(Person.newBuilder(person).setEmail("xin@163.com")
                .build());
        System.out.println("AddressBook對象信息:");
        System.out.println(books.build());
    }
}

編譯後生成的java類是不可變的,相似java的String,不可修改

構造消息,必須先構造一個builder,而後set屬性(能夠一連串的set),最後調用build() 方法。

PB經常使用方法

  • isInitialized(): 檢查必填字段(required)是否有set值
  • toString(): 返回message的可讀字符串格式
  • mergeFrom(Message other): 合併message
  • clear(): 清空字段值
  • byte[] toByteArray();: 序列化message,返回字節數組
  • MessageType parseFrom(byte[] data);: 解析給定的字節數組
  • void writeTo(OutputStream output);: 序列化message並寫入輸出流OutputStream.
  • MessageType parseFrom(InputStream input);: 從輸入流 InputStream讀取並解析message

Reference:

https://github.com/halfrost/H...

相關文章
相關標籤/搜索