序列化:ProtoBuf 與 JSON 的比較 !

介紹java

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.protoui

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)

結果,是能夠轉換成功。

來源:my.oschina.net/xiaolei123/blog/3085607

相關文章
相關標籤/搜索