Protocol Buffers 入門(Android)

1. 關(guān)于 Protobuf

1.1 簡(jiǎn)介

Protocol Buffer戏蔑,簡(jiǎn)稱 Protobuf,是 Google 開發(fā)的一種數(shù)據(jù)描述語(yǔ)言捍壤。它是一種輕便高效的結(jié)構(gòu)化數(shù)據(jù)存儲(chǔ)格式睹逃,適合做數(shù)據(jù)存儲(chǔ)或 RPC 數(shù)據(jù)交換格式,可用于數(shù)據(jù)傳輸量較大的即時(shí)通訊協(xié)議橄碾、數(shù)據(jù)存儲(chǔ)等場(chǎng)景。Protobuf 與語(yǔ)言無(wú)關(guān)、平臺(tái)無(wú)關(guān)埋市,目前提供了多種語(yǔ)言的 API污茵。
官方文檔:https://developers.google.com/protocol-buffers
開源地址:https://github.com/protocolbuffers/protobuf

1.2 優(yōu)勢(shì)
  • 體積小速度快。像 XML 這種報(bào)文是基于文本格式的葬项,存在大量的描述信息泞当,雖然對(duì)于人來(lái)說可讀性更好,但增加了序列化時(shí)間玷室、網(wǎng)絡(luò)傳輸時(shí)間等零蓉。導(dǎo)致系統(tǒng)的整體性能下降笤受。而 PB 則將信息序列化為二進(jìn)制的格式穷缤,安全性提高的同時(shí)敌蜂,序列化后的數(shù)據(jù)大小縮小了3倍,序列化速度比 Json 快了20-100倍津肛,也必然會(huì)減小網(wǎng)絡(luò)傳輸時(shí)間章喉。
  • 跨平臺(tái)跨語(yǔ)言。接收端和發(fā)送端只需要維護(hù)同一份 proto 文件即可身坐。proto 編譯器會(huì)根據(jù)不同的語(yǔ)言秸脱,生成對(duì)應(yīng)的代碼文件。
  • 向后兼容性部蛇。不必破壞舊的數(shù)據(jù)格式摊唇,可以直接對(duì)數(shù)據(jù)結(jié)構(gòu)進(jìn)行更新。

2. 原理分析

通過本質(zhì)探究涯鲁,了解 Protobuf 為何如此高效巷查。

2.1 編碼背景
  • 信源編碼:信源編碼是一種以提高通信有效性為目的而對(duì)信源符號(hào)進(jìn)行的變換,或者說為了減少或消除信源利余度而進(jìn)行的信源符號(hào)變換抹腿。具體說岛请,就是針對(duì)信源輸出符號(hào)序列的統(tǒng)計(jì)特性來(lái)尋找某種方法,把信源輸出符號(hào)序列變換為最短的碼字序列警绩,使后者的各碼元所載荷的平均信息量最大崇败,同時(shí)又能保證無(wú)失真地恢復(fù)原來(lái)的符號(hào)序列。信源編碼的作用之一是肩祥,即通常所說的數(shù)據(jù)壓縮后室;作用之二是將信源的模擬信號(hào)轉(zhuǎn)化成數(shù)字信號(hào),以實(shí)現(xiàn)模擬信號(hào)的數(shù)字化傳輸』旌荩現(xiàn)代通信應(yīng)用中常見的信源編碼方式有:Huffman編碼岸霹、算術(shù)編碼、L-Z編碼檀蹋,這三種都是無(wú)損編碼松申;另外還有一些采用壓縮方式的有損編碼。同時(shí)無(wú)損編碼也根據(jù)"是否把一個(gè)傳輸單位編碼為固定長(zhǎng)度"區(qū)分為定長(zhǎng)編碼和變長(zhǎng)編碼俯逾。定長(zhǎng)編碼就是一個(gè)符號(hào)變換后的碼字的比特長(zhǎng)度是固定的贸桶,比如 ASCII、Unicode 都是定長(zhǎng)編碼桌肴,碼字是8比特皇筛,16比特。變長(zhǎng)編碼則是將信源符號(hào)映射為不同的碼字長(zhǎng)度坠七,比如 Huffman 編碼水醋,PB 編碼旗笔。
  • 信道編碼:信道編碼是為了對(duì)抗信道中的噪音和衰減,通過增加冗余來(lái)提高抗干擾能力以及糾錯(cuò)能力拄踪。信道編碼的本質(zhì)是降低誤碼率蝇恶、增加通信的可靠性。數(shù)字信號(hào)在傳輸中往往由于各種原因惶桐,使得在傳送的數(shù)據(jù)流中產(chǎn)生誤碼撮弧,所以通過信道編碼這一環(huán)節(jié)來(lái)避免碼流傳輸中誤碼的發(fā)生。常用的處理技術(shù)有奇偶校驗(yàn)碼姚糊、糾錯(cuò)碼贿衍、信道交織編碼等。
  • 簡(jiǎn)單來(lái)說救恨,信源編碼就是將信源產(chǎn)生的消息變換為數(shù)字序列的過程贸辈,主要目的是降低數(shù)據(jù)率,提高信息量效率肠槽,一般用來(lái)對(duì)視頻擎淤、音頻、數(shù)據(jù)進(jìn)行處理署浩。而信道編碼的主要目的是提高系統(tǒng)的抗干擾能力揉燃,比如糾錯(cuò)碼啊,卷積碼這類筋栋,可以檢測(cè)出信息是否有被傳錯(cuò)炊汤。
  • 從通信角度來(lái)看,Protobuf 是一種變長(zhǎng)的無(wú)損的信源編碼弊攘。
2.2 整數(shù)的編碼優(yōu)化

varint 編碼:一般情況下抢腐,一個(gè) int 值看作4字節(jié),也就是所謂的定長(zhǎng)編碼襟交。PB 考慮到現(xiàn)實(shí)情況中迈倍,數(shù)值較大的數(shù)比數(shù)值較小的數(shù)更少地被使用這一事實(shí),采用了變長(zhǎng)編碼捣域。如果一個(gè)數(shù)能夠用1個(gè)字節(jié)來(lái)表示啼染,那就用一個(gè)字節(jié)來(lái)表示。如數(shù)值1就會(huì)被編碼為0000 0001焕梅,而不是把它編碼為0000 0000 0000 0000 0000 0000 0000 0001迹鹅。但是也由此產(chǎn)生一個(gè)問題,每個(gè)整數(shù)的編碼長(zhǎng)度可能不一樣贞言,如何區(qū)分邊界呢斜棚?PB 將每個(gè)字節(jié)拿出1比特最高位的那個(gè)比特 MSB(Most Significant Bit)來(lái)作為邊界的標(biāo)記(編碼是否為最后一個(gè)字節(jié)),1表示還沒有到最后一個(gè)字節(jié),0表示到了最后一個(gè)字節(jié)弟蚀。

規(guī)則如下 ↓

  • 0xxx xxxx表示某個(gè)整數(shù)編碼后的結(jié)果是單個(gè)字節(jié)蚤霞,因?yàn)镸SB=0;
  • 1xxx xxxx 0xxx xxxx表示某個(gè)整數(shù)編碼后的結(jié)果是2個(gè)字節(jié)义钉,因?yàn)榍耙粋€(gè)字節(jié)的MSB=1(編碼結(jié)果未結(jié)束)昧绣,后一個(gè)字節(jié)的MSB=0;
  • 同理断医,三個(gè)字節(jié)滞乙、四個(gè)字節(jié)都用這種方法來(lái)表示邊界奏纪。

代碼如下 ↓

final void bufferUInt32NoTag(int value) {
    if (HAS_UNSAFE_ARRAY_OPERATIONS) {
        final long originalPos = position;
        while (true) {
            if ((value & ~0x7F) == 0) {
                //最后一次取出最高位補(bǔ)0
                UnsafeUtil.putByte(buffer, position++, (byte) value);
                break;
            } else {
                UnsafeUtil.putByte(buffer, position++, (byte) ((value & 0x7F) | 0x80));
                //取出后面7位鉴嗤,最高位補(bǔ)1
                value >>>= 7;
            }
        }
        int delta = (int) (position - originalPos);
        totalBytesWritten += delta;
    } else {
        while (true) {
            if ((value & ~0x7F) == 0) {
                buffer[position++] = (byte) value;
                totalBytesWritten++;
                return;
            } else {
                buffer[position++] = (byte) ((value & 0x7F) | 0x80);
                totalBytesWritten++;
                value >>>= 7;
            }
        }
    }
}

示例如下 ↓

  • 0000 0001表示整數(shù)1;
  • 1010 1100 0000 0010表示兩個(gè)字節(jié)的結(jié)果序调。將兩字節(jié)的MSB去掉為:0101100 0000010醉锅。由于 PB 對(duì)于多個(gè)字節(jié)的情況采用低字節(jié)優(yōu)先,即后面的字節(jié)要放在高位发绢,于是拼在一起的結(jié)果為:00000100101100硬耍,表示300這個(gè)整數(shù)值。(其實(shí)就是將數(shù)字的二進(jìn)制補(bǔ)碼的每7位分為一組边酒, 低7位先輸出经柴,編碼在前面,在輸出下一組墩朦,依次類推)

可以看到以上的變長(zhǎng)編碼方式坯认,在數(shù)據(jù)壓縮上能節(jié)省很多空間,不過它也存在以下幾個(gè)小缺點(diǎn) ↓

  • 造成了比特的1/8的浪費(fèi)氓涣,一個(gè)很大的數(shù)將可能使用5個(gè)字節(jié)來(lái)表示牛哺。
  • 負(fù)數(shù)需要10個(gè)字節(jié)顯示(因?yàn)樨?fù)數(shù)最高位是1,會(huì)被當(dāng)作很大的整數(shù)處理)
2.3 對(duì)象的編碼優(yōu)化
2.3.1 Protobuf 對(duì) key-value 中 key 的優(yōu)化:使用序號(hào) key 代替變量名

對(duì)于一個(gè)對(duì)象劳吠,里面包含多個(gè)變量引润,如何編碼呢?假設(shè)一個(gè)類的定義如下 ↓

Class Student {
    String name;
    String sex;
    int age;
}

如果使用 XML痒玩,那么傳輸?shù)母袷饺缦?↓

<?xml version="1.0" encoding="UTF-8" ?>
        <name>Bob</name>
        <sex>male</sex>
        <age>18</age>

如果使用 Json淳附,那么傳輸?shù)母袷饺缦?↓

{
    "name": "Bob",
    "sex": "male",
    "age": "18"
}

而 Protobuf 認(rèn)為 "name"洛姑、"sex"严拒、"age" 這些變量名不應(yīng)該包含在傳輸消息中,因?yàn)榫幗獯a追他、傳輸這些信息也需要資源便瑟。Protobuf 為了節(jié)省空間缆毁,在通信雙方都保持一份文檔,記錄了變量名的編號(hào)到涂,比如上述三個(gè)變量名字分別編號(hào)為1脊框、2颁督、3。于是在序列化的時(shí)候浇雹,只需要傳輸下面的信息 ↓

1:"Bob", 2:"male", 3:"18"

由于對(duì)方也保留了一份編號(hào)文檔沉御,于是就可以反序列化了。這些編號(hào)本身也可以用上面對(duì)整數(shù)的編碼優(yōu)化方式進(jìn)行編碼昭灵。??

2.3.2 Protobuf 對(duì) key-value 中 value 的優(yōu)化

如果 value 為整數(shù)吠裆,那么直接使用前面提到的對(duì)整數(shù)的編碼優(yōu)化即可。即大多數(shù)整數(shù)只占一兩個(gè)字節(jié)烂完。
如果 value 為字符串试疙,這時(shí)候每個(gè)字節(jié)都拿出1個(gè) bit 來(lái)區(qū)分邊界就太浪費(fèi)空間了,而且字符串本身就是一個(gè)一個(gè)字節(jié)的抠蚣,被打亂后也會(huì)影響解碼效率祝旷。因此,Protobuf 將 value 長(zhǎng)度信息的指示可以放在 key 和 value 之間(長(zhǎng)度本身也是一個(gè)整數(shù)嘶窄,也能編碼優(yōu)化)怀跛。在解碼 value 時(shí),解析長(zhǎng)度就可以知道 value 值到哪里結(jié)束柄冲。不過也因此產(chǎn)生一個(gè)問題吻谋,比如整數(shù)這種情況,value 中已經(jīng)自帶了結(jié)束標(biāo)識(shí)符现横,那就不需要 value 的長(zhǎng)度指示信息了漓拾。因此 Protobuf 引入了 Type 類型,即提前告訴接收端 value 的類型长赞。Protobuf 將這個(gè) Type 信息放在了 key 中的最后 3 個(gè) bit 中晦攒。根據(jù)這個(gè) Type,即可讓接受端注意或者忽略 value 的長(zhǎng)度字段得哆。value 的類型在 Protobuf 中稱為 wire_type脯颜。主要有以下幾種 ↓

wire type = 0  
// 0 表示這個(gè)Value是一個(gè)變長(zhǎng)整數(shù),比如int32, int64, uint32, uint64, sint32, sint64, bool, enum
wire type = 1
// 1 表示這個(gè)Value是一個(gè)64位的定長(zhǎng)數(shù)贩据,比如fixed64, sfixed64, double
wire type = 2
// 2 表示string, bytes等栋操,這些Value的長(zhǎng)度需要置于Key后面
wire type = 3
// 3 表示groups中的Start Group,就是有一組饱亮,3表示接下來(lái)的Value是第一組
wire type = 4
// 4 表示groups中的End Group
wire type = 5
// 5 表示32位固定長(zhǎng)度的fixed32, sfixed32, float等
2.4 示例
  • 例子1:08 ac 02 這三個(gè)字節(jié)矾芙,分析如下 ↓
  1. 08 二進(jìn)制為 0000 1000,最高位 0 表示這是最后一個(gè)字節(jié)近上,去除最高位為 0001 000剔宪。
  2. 最后 3 個(gè) bit 為 Type 類型,000 表示 wire type = 0,前面的 0001 表示這是編號(hào)為1的變量葱绒。
  3. 后面的 ac 02感帅,寫成二進(jìn)制為 10101100 00000010,去掉最高位分隔符為 0101100 0000010地淀,因?yàn)榈妥止?jié)優(yōu)先失球,于是串起來(lái)為 0000010 0101100 = 300。
  4. 最終帮毁,08 ac 02 這三個(gè)字節(jié)解碼為編號(hào)為 1 的變量值為整數(shù) 300实苞。
  • 例子2:12 07 74 65 73 74 69 6e 67 這九個(gè)字節(jié),分析如下 ↓
  1. 12 的二進(jìn)制為 0001 0010烈疚,最高位 0 表示這是最后一個(gè)字節(jié)黔牵,去除最高位為 0010 010。
  2. 最后 3 個(gè) bit 010 表示 wire type = 2胞得,前四位 0010 表示這是編號(hào)為 2 的變量荧止。
  3. 因?yàn)閣ire type = 2,表示 value 是 String阶剑、bytes 等變長(zhǎng)流。接下來(lái)要解碼 value 的長(zhǎng)度危号。
  4. 07 的二進(jìn)制為 0000 0111牧愁,最高位為 0,表示這是最后一個(gè)字節(jié)外莲,去除最高位后是 000 0111猪半,表示Value的長(zhǎng)度為 7,也就是后面的 7 個(gè)字節(jié):74 65 73 74 69 6e 67偷线。
  5. 這 7 個(gè)字節(jié)如果是 String磨确,那么根據(jù) ASCII 碼可解碼為:"testing"。
  6. 最終声邦,12 07 74 65 73 74 69 6e 67 這幾個(gè)字節(jié)解碼為編號(hào)為 2 的變量值為字符串"testing"乏奥。
2.5 總結(jié)
2.5.1 體積壓縮優(yōu)勢(shì)

由 2.4 的第二個(gè)例子,08 ac 02 12 07 74 65 73 74 69 6e 67 這九個(gè)字節(jié)等價(jià)于 Json 中的 ↓

{"testInt":"300", "testString":"testing"}

可看出亥曹,Json 使用了40個(gè)左右的字節(jié)邓了,而 Protocol 只使用了12個(gè)字節(jié),這也就解釋了為什么 Protobuf 將信息序列化為二進(jìn)制后媳瞪,體積縮小了3倍骗炉,也因此減少了數(shù)據(jù)網(wǎng)絡(luò)傳輸?shù)臅r(shí)間。

2.5.2 序列化速度優(yōu)勢(shì)

以 XML 的解包過程為例蛇受,XML 首先需要將得到的字符串轉(zhuǎn)換為 XML 文檔對(duì)象的結(jié)構(gòu)模型句葵,再?gòu)慕Y(jié)構(gòu)模型中讀取指定節(jié)點(diǎn)的字符串,最后再將這個(gè)字符串指定為某個(gè)對(duì)象的變量值。這個(gè)過程非常復(fù)雜乍丈,其中轉(zhuǎn)換文檔對(duì)象結(jié)構(gòu)模型的過程熊响,通常需要完成詞法文法分析等大量消耗 CPU 的復(fù)雜計(jì)算。而 Protobuf 只需要簡(jiǎn)單地將一個(gè)二進(jìn)制序列诗赌,按照指定的格式讀取賦值到某個(gè)對(duì)象的變量值即可汗茄。因此它的的序列化速度非常快铭若。

3. 在 Android 中的簡(jiǎn)單使用

分析完 Protobuf 的原理之后洪碳,便是開始學(xué)習(xí)如何使用。前面說到它可用于多種語(yǔ)言叼屠,且與平臺(tái)無(wú)關(guān)瞳腌。不過我只稍微學(xué)習(xí)了如何在 Android studio 中使用 Protobuf。

3.1 Gradle 配置

在根目錄的 build.gradle 中添加如下代碼 ↓

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.2.1'
        classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.6' // 添加這行
    }
}

在 module 的 build.gradle 中首先添加如下代碼 ↓

apply plugin: 'com.google.protobuf' // 添加插件

接著添加 protobuf 塊(與android同級(jí))↓

protobuf {
    // 配置protoc編譯器
    protoc {
        artifact = 'com.google.protobuf:protoc:3.0.0-alpha-3'
    }
    plugins {
        javalite {
            artifact = 'com.google.protobuf:protoc-gen-javalite:3.0.0'
        }
    }
    // 這里配置生成目錄镜雨,編譯后會(huì)在build的目錄下生成對(duì)應(yīng)的java文件
    generateProtoTasks {
        all().each { task ->
            task.plugins {
                javalite {}
            }
        }
    }
}

再添加依賴 ↓

implementation 'com.google.protobuf:protobuf-lite:3.0.0'

此時(shí)可以編譯項(xiàng)目嫂侍,會(huì)生成 proto java class,這個(gè)類就是我們后面所要使用到的荚坞。

3.2 定義 proto 文件

一般是在 java/res 同級(jí)目錄下建立 proto 文件夾挑宠,然后再創(chuàng)建 .proto 文件。



我們?cè)?.proto 文件里定義數(shù)據(jù)結(jié)構(gòu)颓影。這里有份官網(wǎng)的示例代碼 ↓

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;
}

關(guān)鍵字說明 ↓ (更多內(nèi)容可以參考官網(wǎng) Protocol Buffer Language Guide

syntax 聲明版本各淀。例如上面代碼的syntax="proto3",如果沒有聲明诡挂,則默認(rèn)是proto2
package 聲明包名
import 導(dǎo)入包
java_package 指定生成的類應(yīng)該放在什么Java包名下碎浇。如果你沒有顯式地指定這個(gè)值,則它簡(jiǎn)單地匹配由package 聲明給出的Java包名璃俗,但這些名字通常都不是合適的Java包名 (由于它們通常不以一個(gè)域名打頭)
java_outer_classname 定義應(yīng)該包含這個(gè)文件中所有類的類名奴璃。如果你沒有顯式地給定java_outer_classname ,則將通過把文件名轉(zhuǎn)換為首字母大寫來(lái)生成城豁。例如上面例子編譯生成的文件名和類名是AddressBookProtos
message 類似于java中的class關(guān)鍵字
repeated 用于修飾屬性苟穆,表示對(duì)應(yīng)的屬性是個(gè)array
optional 可選字段,可以不傳入數(shù)據(jù)钮蛛,或者設(shè)置默認(rèn)值
required 必填字段鞭缭,如果創(chuàng)建數(shù)據(jù)對(duì)象時(shí)不傳入?yún)?shù),編碼時(shí)就會(huì)拋出exception魏颓。使用required時(shí)要注意岭辣,如果你升級(jí)協(xié)議時(shí)把這個(gè)字段改為optional,接收方?jīng)]有升級(jí)甸饱,你發(fā)送的數(shù)據(jù)對(duì)方將無(wú)法解釋沦童。因此不建議使用它仑濒,一般只使用optional和repeated

這里我跟著教程,定義了一個(gè)簡(jiǎn)單的數(shù)據(jù)結(jié)構(gòu) ↓

syntax = "proto3";
package tutorial;

message Person {
    string name = 1;
    int32 id = 2;
    string email = 3;
    string phone = 4;
}

編譯后偷遗,在以下的目錄中會(huì)生成對(duì)應(yīng)的 java 文件 ↓



Dataformat.java 即是 dataformat.proto 生成的對(duì)應(yīng) java 文件墩瞳,里面代碼行數(shù)有點(diǎn)多。

3.3 獲取數(shù)據(jù)

通過網(wǎng)絡(luò)獲取數(shù)據(jù)流氏豌,然后解析成 proto 文件定義的格式 ↓

Observable.just("http://elyeproject.x10host.com/experiment/protobuf")
        .map(new Function<String, Dataformat.Person>() {
            @Override
            public Dataformat.Person apply(String url) throws Exception {
                OkHttpClient okHttpClient = new OkHttpClient();
                Request request = new Request.Builder().url(url).build();
                Call call = okHttpClient.newCall(request);
                Response response = call.execute();
                if (response.isSuccessful()) {
                    ResponseBody responseBody = response.body();
                    if (responseBody != null) {
                        return Dataformat.Person.parseFrom(responseBody.byteStream());
                    }
                }

                return null;
            }
        }).subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(new Consumer<Dataformat.Person>() {
            @Override
            public void accept(Dataformat.Person person) throws Exception {
                Log.i(TAG, person.getName());
            }
        }, new Consumer<Throwable>() {
            @Override
            public void accept(Throwable throwable) throws Exception {
                Log.i(TAG, throwable.getMessage());
            }
        });

該網(wǎng)站返回的數(shù)據(jù)如下 ↓


Android 獲取的數(shù)據(jù)如下 ↓

PS:doc-android-client 項(xiàng)目的 bitable module 主要實(shí)現(xiàn) native UI喉酌,它所引入的 bitable_bridge 包則負(fù)責(zé)業(yè)務(wù)邏輯。bitable_bridge 是個(gè) React Native 工程泵喘,邏輯是用 JS 寫的泪电,數(shù)據(jù)格式則為 Protobuf 。

4. 數(shù)據(jù)交互格式比較

4.1 XML纪铺、Json相速、Protobuf
Json 一般的 web 項(xiàng)目中,最流行的主要還是 Json鲜锚。因?yàn)闉g覽器對(duì)于 Json 數(shù)據(jù)支持非常友好突诬,有很多內(nèi)建的函數(shù)支持。 Json 使用了鍵值對(duì)的方式芜繁,不僅壓縮了一定的數(shù)據(jù)空間旺隙,同時(shí)也具有可讀性。
XML 在 webservice 中應(yīng)用最為廣泛浆洗,但是相比于 Json催束,它的數(shù)據(jù)更加冗余,因?yàn)樾枰蓪?duì)的閉合標(biāo)簽伏社。
Protobuf 后起之秀,適合高性能塔淤,對(duì)響應(yīng)速度有要求的數(shù)據(jù)傳輸場(chǎng)景摘昌。因?yàn)槭嵌M(jìn)制數(shù)據(jù)格式,需要編碼和解碼高蜂。數(shù)據(jù)本身不具有可讀性聪黎。因此只能反序列化之后得到真正可讀的數(shù)據(jù)。

相對(duì)于其他兩種語(yǔ)言备恤,Protobuf 具有的優(yōu)勢(shì)如下 ↓

  1. 序列化后體積相比 Json 和 XML 很小稿饰,適合網(wǎng)絡(luò)傳輸;
  2. 支持跨平臺(tái)多語(yǔ)言露泊;
  3. 消息格式升級(jí)和兼容性還不錯(cuò)喉镰;
  4. 序列化和反序列化速度很快,快于 Json 的處理速度惭笑。

PS:雖然 Protobuf 并非像 Json 那樣直接明文顯示侣姆,不過我們只需定義對(duì)象結(jié)構(gòu)生真,然后由 Protbuf 庫(kù)去把對(duì)象自動(dòng)轉(zhuǎn)換成二進(jìn)制,用的時(shí)候再自動(dòng)反解碼過來(lái)捺宗。傳輸過程于我們而言是透明的柱蟀。我們只負(fù)責(zé)傳輸?shù)膶?duì)象就可以了,所以用起來(lái)很方便蚜厉。

結(jié)論:在一個(gè)需要大量的數(shù)據(jù)傳輸?shù)膱?chǎng)景中长已,如果數(shù)據(jù)量很大,那么選擇 Protobuf 可以明顯地減少數(shù)據(jù)量昼牛,減少網(wǎng)絡(luò) IO术瓮,從而減少傳輸所消耗的時(shí)間。

4.2 Protobuf 與 Json 速度比較
測(cè)試平臺(tái) Android studio 3.2
所引用的庫(kù) google.protobuf(proto3)匾嘱、google.gson.Gson
目的 比較 Protobuf 與 Json 的序列化/反序列化速度
方法 控制變量法

過程簡(jiǎn)述 ↓

  1. 為 Protobuf 和 Json 創(chuàng)建一樣的數(shù)據(jù)結(jié)構(gòu)(.proto 文件 和 .class 文件)斤斧,然后存進(jìn)以下的數(shù)據(jù),當(dāng)然這些數(shù)據(jù)可以多次賦值霎烙、多次測(cè)試撬讽。
String name = "王小明";
int id = 15331016;
String email = "chen@bytedance.com";
String phone = "12345678910";
  1. Protobuf 操作
Dataformat.Person.Builder builder = Dataformat.Person.newBuilder();
// 存進(jìn)數(shù)據(jù)
builder.setName(name);
builder.setId(id);
builder.setEmail(email);
builder.setPhone(phone);
// 序列化
Dataformat.Person person_write = builder.build();
byte[] result = person_write.toByteArray();
// 反序列化
Dataformat.Person person_read = Dataformat.Person.parseFrom(result);

可以看到,protocol 序列化后得到的編碼結(jié)果為 49 個(gè)字節(jié)悬垃。



  1. Json 操作
Gson gson = new Gson();
// 存進(jìn)數(shù)據(jù)
Person person1 = new Person(name, id, email, phone);
// 序列化
String person1_write = gson.toJson(person1);
// 反序列化
Person person1_read = gson.fromJson(person1_write, Person.class);

序列化后的大小為 82 個(gè)字節(jié)游昼。


  1. Log輸出每個(gè)節(jié)點(diǎn)的時(shí)間,為了效果明顯尝蠕,每個(gè)(反)序列化操作重復(fù)進(jìn)行1000000次

    結(jié)果分析 ↓ (單位:ms)

    結(jié)論 ↓
    Protobuf 的體積比 Json 小烘豌,(反)序列化速度比 Json 快,在數(shù)據(jù)傳輸上更具優(yōu)勢(shì)看彼。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末廊佩,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子靖榕,更是在濱河造成了極大的恐慌标锄,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,013評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件茁计,死亡現(xiàn)場(chǎng)離奇詭異料皇,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)星压,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,205評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門践剂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人娜膘,你說我怎么就攤上這事逊脯。” “怎么了劲绪?”我有些...
    開封第一講書人閱讀 152,370評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵男窟,是天一觀的道長(zhǎng)盆赤。 經(jīng)常有香客問我,道長(zhǎng)歉眷,這世上最難降的妖魔是什么牺六? 我笑而不...
    開封第一講書人閱讀 55,168評(píng)論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮汗捡,結(jié)果婚禮上淑际,老公的妹妹穿的比我還像新娘。我一直安慰自己扇住,他們只是感情好春缕,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,153評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著艘蹋,像睡著了一般锄贼。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上女阀,一...
    開封第一講書人閱讀 48,954評(píng)論 1 283
  • 那天宅荤,我揣著相機(jī)與錄音,去河邊找鬼浸策。 笑死冯键,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的庸汗。 我是一名探鬼主播惫确,決...
    沈念sama閱讀 38,271評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼蚯舱!你這毒婦竟也來(lái)了改化?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,916評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤枉昏,失蹤者是張志新(化名)和其女友劉穎所袁,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體凶掰,經(jīng)...
    沈念sama閱讀 43,382評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,877評(píng)論 2 323
  • 正文 我和宋清朗相戀三年蜈亩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了懦窘。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,989評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡稚配,死狀恐怖畅涂,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情道川,我是刑警寧澤午衰,帶...
    沈念sama閱讀 33,624評(píng)論 4 322
  • 正文 年R本政府宣布立宜,位于F島的核電站,受9級(jí)特大地震影響臊岸,放射性物質(zhì)發(fā)生泄漏橙数。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,209評(píng)論 3 307
  • 文/蒙蒙 一帅戒、第九天 我趴在偏房一處隱蔽的房頂上張望灯帮。 院中可真熱鬧,春花似錦逻住、人聲如沸钟哥。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,199評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)腻贰。三九已至,卻和暖如春扒秸,著一層夾襖步出監(jiān)牢的瞬間播演,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,418評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工鸦采, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留宾巍,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,401評(píng)論 2 352
  • 正文 我出身青樓渔伯,卻偏偏與公主長(zhǎng)得像顶霞,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子锣吼,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,700評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容