介紹
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次,耗時:22ms
JSON 資料長度:106
-開始解碼-
JSON 解碼1次,耗時:1ms
【 ProtoBuf 開始編碼 】
ProtoBuf 編碼1次,耗時:32ms
ProtoBuf 資料長度:34
-開始解碼-
ProtoBuf 解碼1次,耗時:3ms
-
運行 10 次
【 JSON 開始編碼 】
JSON 編碼10次,耗時:22ms
JSON 資料長度:106
-開始解碼-
JSON 解碼10次,耗時:4ms
【 ProtoBuf 開始編碼 】
ProtoBuf 編碼10次,耗時:29ms
ProtoBuf 資料長度:34
-開始解碼-
ProtoBuf 解碼10次,耗時:3ms
-
運行 100 次
【 JSON 開始編碼 】
JSON 編碼100次,耗時:32ms
JSON 資料長度:106
-開始解碼-
JSON 解碼100次,耗時:8ms
【 ProtoBuf 開始編碼 】
ProtoBuf 編碼100次,耗時:31ms
ProtoBuf 資料長度:34
-開始解碼-
ProtoBuf 解碼100次,耗時:4ms
-
運行 1000 次
【 JSON 開始編碼 】
JSON 編碼1000次,耗時:39ms
JSON 資料長度:106
-開始解碼-
JSON 解碼1000次,耗時:21ms
【 ProtoBuf 開始編碼 】
ProtoBuf 編碼1000次,耗時:37ms
ProtoBuf 資料長度:34
-開始解碼-
ProtoBuf 解碼1000次,耗時:8ms
-
運行 1萬 次
【 JSON 開始編碼 】
JSON 編碼10000次,耗時:126ms
JSON 資料長度:106
-開始解碼-
JSON 解碼10000次,耗時:93ms
【 ProtoBuf 開始編碼 】
ProtoBuf 編碼10000次,耗時:49ms
ProtoBuf 資料長度:34
-開始解碼-
ProtoBuf 解碼10000次,耗時:23ms
-
運行 10萬 次
【 JSON 開始編碼 】
JSON 編碼100000次,耗時:248ms
JSON 資料長度:106
-開始解碼-
JSON 解碼100000次,耗時:180ms
【 ProtoBuf 開始編碼 】
ProtoBuf 編碼100000次,耗時:51ms
ProtoBuf 資料長度: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)
結果,是可以轉換成功,
END
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/95202.html
標籤:其他
