首頁>技術>

介紹

ProtoBuf 是google團隊開發的用於高效儲存和讀取結構化資料的工具。什麼是結構化資料呢,正如字面上表達的,就是帶有一定結構的資料。比如電話簿上有很多記錄資料,每條記錄包含姓名、ID、郵件、電話等,這種結構重複出現。

同類

XML、JSON 也可以用來儲存此類結構化資料,但是使用ProtoBuf表示的資料能更加高效,並且將資料壓縮得更小。

原理

ProtoBuf 是透過ProtoBuf編譯器將與程式語言無關的特有的 .proto 字尾的資料結構檔案編譯成各個程式語言(Java,C/C++,Python)專用的類檔案,然後透過Google提供的各個程式語言的支援庫lib即可呼叫API。(關於proto結構體怎麼編寫,可自行查閱文件)

ProtoBuf編譯器安裝

Mac : brew install protobuf

舉個例子

1.先建立一個proto檔案message.proto

syntax = "proto3";message Person {    int32 id = 1;    string name = 2;    repeated Phone phone = 4;    enum PhoneType {        MOBILE = 0;        HOME = 1;        WORK = 2;    }    message Phone {        string number = 1;        PhoneType type = 2;    }}

2.建立一個Java專案並且將proto檔案放置 src/main/proto 資料夾下

3.編譯proto檔案至Java版本用命令列 cd 到 src/main 目錄下

終端執行命令 : protoc --java_out=./java ./proto/*.proto

會發現,在你的src/main/java 裡已經生成裡對應的Java類

4.依賴Java版本的ProtoBuf支援庫這裡只舉一個用Gradle使用依賴的栗子

implementation 'com.google.protobuf:protobuf-java:3.9.1'

5.將Java物件轉為ProtoBuf資料

Message.Person.Phone.Builder phoneBuilder = Message.Person.Phone.newBuilder();Message.Person.Phone phone1 = phoneBuilder        .setNumber("100860")        .setType(Message.Person.PhoneType.HOME)        .build();Message.Person.Phone phone2 = phoneBuilder        .setNumber("100100")        .setType(Message.Person.PhoneType.MOBILE)        .build();Message.Person.Builder personBuilder = Message.Person.newBuilder();personBuilder.setId(1994);personBuilder.setName("XIAOLEI");personBuilder.addPhone(phone1);personBuilder.addPhone(phone2);Message.Person person = personBuilder.build();long old = System.currentTimeMillis();byte[] buff = person.toByteArray();System.out.println("ProtoBuf 編碼耗時:" + (System.currentTimeMillis() - old));System.out.println(Arrays.toString(buff));System.out.println("ProtoBuf 資料長度:" + buff.length);

6.將ProtoBuf資料,轉換回Java物件

System.out.println("-開始解碼-");old = System.currentTimeMillis();Message.Person personOut = Message.Person.parseFrom(buff);System.out.println("ProtoBuf 解碼耗時:" + (System.currentTimeMillis() - old));System.out.printf("Id:%d, Name:%s\n", personOut.getId(), personOut.getName());List<Message.Person.Phone> phoneList = personOut.getPhoneList();for (Message.Person.Phone phone : phoneList){    System.out.printf("手機號:%s (%s)\n", phone.getNumber(), phone.getType());}

比較

為了能體現ProtoBuf的優勢,我寫了同樣結構體的Java類,並且將Java物件轉換成JSON資料,來與ProtoBuf進行比較。JSON編譯庫使用Google提供的GSON庫,JSON的部分程式碼就不貼出來了,直接展示結果

比較結果結果

執行 1 次

【 JSON 開始編碼 】JSON 編碼1次,耗時:22msJSON 資料長度:106-開始解碼-JSON 解碼1次,耗時:1ms【 ProtoBuf 開始編碼 】ProtoBuf 編碼1次,耗時:32msProtoBuf 資料長度:34-開始解碼-ProtoBuf 解碼1次,耗時:3ms

執行 10 次

【 JSON 開始編碼 】JSON 編碼10次,耗時:22msJSON 資料長度:106-開始解碼-JSON 解碼10次,耗時:4ms【 ProtoBuf 開始編碼 】ProtoBuf 編碼10次,耗時:29msProtoBuf 資料長度:34-開始解碼-ProtoBuf 解碼10次,耗時:3ms

執行 100 次

【 JSON 開始編碼 】JSON 編碼100次,耗時:32msJSON 資料長度:106-開始解碼-JSON 解碼100次,耗時:8ms【 ProtoBuf 開始編碼 】ProtoBuf 編碼100次,耗時:31msProtoBuf 資料長度:34-開始解碼-ProtoBuf 解碼100次,耗時:4ms

執行 1000 次

【 JSON 開始編碼 】JSON 編碼1000次,耗時:39msJSON 資料長度:106-開始解碼-JSON 解碼1000次,耗時:21ms【 ProtoBuf 開始編碼 】ProtoBuf 編碼1000次,耗時:37msProtoBuf 資料長度:34-開始解碼-ProtoBuf 解碼1000次,耗時:8ms

執行 1萬 次

【 JSON 開始編碼 】JSON 編碼10000次,耗時:126msJSON 資料長度:106-開始解碼-JSON 解碼10000次,耗時:93ms【 ProtoBuf 開始編碼 】ProtoBuf 編碼10000次,耗時:49msProtoBuf 資料長度:34-開始解碼-ProtoBuf 解碼10000次,耗時:23ms

執行 10萬 次

【 JSON 開始編碼 】JSON 編碼100000次,耗時:248msJSON 資料長度:106-開始解碼-JSON 解碼100000次,耗時:180ms【 ProtoBuf 開始編碼 】ProtoBuf 編碼100000次,耗時:51msProtoBuf 資料長度:34-開始解碼-ProtoBuf 解碼100000次,耗時:58ms

總結

編解碼效能上述栗子只是簡單的取樣,實際上據我的實驗發現

次數在1千以下,ProtoBuf 的編碼與解碼效能,都與JSON不相上下,甚至還有比JSON差的趨勢。次數在2千以上,ProtoBuf的編碼解碼效能,都比JSON高出很多。次數在10萬以上,ProtoBuf的編解碼效能就很明顯了,遠遠高出JSON的效能。

記憶體佔用ProtoBuf的記憶體34,而JSON到達106 ,ProtoBuf的記憶體佔用只有JSON的1/3.

結尾

其實這次實驗有很多可待最佳化的地方,就算是這種粗略的測試,也能看出來ProtoBuf的優勢。

相容

新增欄位

在proto檔案中新增 nickname 欄位生成Java檔案用老proto位元組陣列資料,轉換成物件
Id:1994, Name:XIAOLEI手機號:100860 (HOME)手機號:100100 (MOBILE)getNickname=

結果,是可以轉換成功。

在proto檔案中刪除 name 欄位生成Java檔案用老proto位元組陣列資料,轉換成物件
Id:1994, Name:null手機號:100860 (HOME)手機號:100100 (MOBILE)

結果,是可以轉換成功。

18
最新評論
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • JVM的垃圾回收演算法詳解,不看實在是太虧了