Google Protocol Buffer 的使用和原理[轉]

本文轉自: http://www.ibm.com/developerworks/cn/linux/l-cn-gpb/

Protocol Buffers 是一種輕便高效的結構化數據存儲格式,能夠用於結構化數據串行化,很適合作數據存儲或 RPC 數據交換格式。它可用於通信協議、數據存儲等領域的語言無關、平臺無關、可擴展的序列化結構數據格式。目前提供了 C++、Java、Python 三種語言的 API。

簡介

什麼是 Google Protocol Buffer? 假如您在網上搜索,應該會獲得相似這樣的文字介紹:html

Google Protocol Buffer( 簡稱 Protobuf) 是 Google 公司內部的混合語言數據標準,目前已經正在使用的有超過 48,162 種報文格式定義和超過 12,183 個 .proto 文件。他們用於 RPC 系統和持續數據存儲系統。前端

Protocol Buffers 是一種輕便高效的結構化數據存儲格式,能夠用於結構化數據串行化,或者說序列化。它很適合作數據存儲或 RPC 數據交換格式。可用於通信協議、數據存儲等領域的語言無關、平臺無關、可擴展的序列化結構數據格式。目前提供了 C++、Java、Python 三種語言的 API。java

或許您和我同樣,在第一次看完這些介紹後仍是不明白 Protobuf 到底是什麼,那麼我想一個簡單的例子應該比較有助於理解它。linux

 

一個簡單的例子

安裝 Google Protocol Buffer

在網站 http://code.google.com/p/protobuf/downloads/list上能夠下載 Protobuf 的源代碼。而後解壓編譯安裝即可以使用它了。程序員

安裝步驟以下所示:數據庫

 tar -xzf protobuf-2.1.0.tar.gz 
 cd protobuf-2.1.0 
 ./configure --prefix=$INSTALL_DIR 
 make 
 make check 
 make install

關於簡單例子的描述

我打算使用 Protobuf 和 C++ 開發一個十分簡單的例子程序。編程

該程序由兩部分組成。第一部分被稱爲 Writer,第二部分叫作 Reader。後端

Writer 負責將一些結構化的數據寫入一個磁盤文件,Reader 則負責從該磁盤文件中讀取結構化數據並打印到屏幕上。api

準備用於演示的結構化數據是 HelloWorld,它包含兩個基本數據:

  • ID,爲一個整數類型的數據
  • Str,這是一個字符串

書寫 .proto 文件

首先咱們須要編寫一個 proto 文件,定義咱們程序中須要處理的結構化數據,在 protobuf 的術語中,結構化數據被稱爲 Message。proto 文件很是相似 java 或者 C 語言的數據定義。代碼清單 1 顯示了例子應用中的 proto 文件內容。

清單 1. proto 文件
 package lm; 
 message helloworld 
 { 
    required int32     id = 1;  // ID 
    required string    str = 2;  // str 
    optional int32     opt = 3;  //optional field 
 }

一個比較好的習慣是認真對待 proto 文件的文件名。好比將命名規則定於以下:

 packageName.MessageName.proto

在上例中,package 名字叫作 lm,定義了一個消息 helloworld,該消息有三個成員,類型爲 int32 的 id,另外一個爲類型爲 string 的成員 str。opt 是一個可選的成員,即消息中能夠不包含該成員。

編譯 .proto 文件

寫好 proto 文件以後就能夠用 Protobuf 編譯器將該文件編譯成目標語言了。本例中咱們將使用 C++。

假設您的 proto 文件存放在 $SRC_DIR 下面,您也想把生成的文件放在同一個目錄下,則可使用以下命令:

 protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/addressbook.proto

命令將生成兩個文件:

lm.helloworld.pb.h , 定義了 C++ 類的頭文件

lm.helloworld.pb.cc , C++ 類的實現文件

在生成的頭文件中,定義了一個 C++ 類 helloworld,後面的 Writer 和 Reader 將使用這個類來對消息進行操做。諸如對消息的成員進行賦值,將消息序列化等等都有相應的方法。

編寫 writer 和 Reader

如前所述,Writer 將把一個結構化數據寫入磁盤,以便其餘人來讀取。假如咱們不使用 Protobuf,其實也有許多的選擇。一個可能的方法是將數據轉換爲字符串,而後將字符串寫入磁盤。轉換爲字符串的方法可使用 sprintf(),這很是簡單。數字 123 能夠變成字符串」123」。

這樣作彷佛沒有什麼不妥,可是仔細考慮一下就會發現,這樣的作法對寫 Reader 的那我的的要求比較高,Reader 的做者必須了 Writer 的細節。好比」123」能夠是單個數字 123,但也能夠是三個數字 1,2 和 3,等等。這麼說來,咱們還必須讓 Writer 定義一種分隔符同樣的字符,以便 Reader 能夠正確讀取。但分隔符也許還會引發其餘的什麼問題。最後咱們發現一個簡單的 Helloworld 也須要寫許多處理消息格式的代碼。

若是使用 Protobuf,那麼這些細節就能夠不須要應用程序來考慮了。

使用 Protobuf,Writer 的工做很簡單,須要處理的結構化數據由 .proto 文件描述,通過上一節中的編譯過程後,該數據化結構對應了一個 C++ 的類,並定義在 lm.helloworld.pb.h 中。對於本例,類名爲 lm::helloworld。

Writer 須要 include 該頭文件,而後即可以使用這個類了。

如今,在 Writer 代碼中,將要存入磁盤的結構化數據由一個 lm::helloworld 類的對象表示,它提供了一系列的 get/set 函數用來修改和讀取結構化數據中的數據成員,或者叫 field。

當咱們須要將該結構化數據保存到磁盤上時,類 lm::helloworld 已經提供相應的方法來把一個複雜的數據變成一個字節序列,咱們能夠將這個字節序列寫入磁盤。

對於想要讀取這個數據的程序來講,也只須要使用類 lm::helloworld 的相應反序列化方法來將這個字節序列從新轉換會結構化數據。這同咱們開始時那個「123」的想法相似,不過 Protobuf 想的遠遠比咱們那個粗糙的字符串轉換要全面,所以,咱們不如放心將這類事情交給 Protobuf 吧。

程序清單 2 演示了 Writer 的主要代碼,您必定會以爲很簡單吧?

清單 2. Writer 的主要代碼
 #include "lm.helloworld.pb.h"
…

 int main(void) 
 { 
  
  lm::helloworld msg1; 
  msg1.set_id(101); 
  msg1.set_str(「hello」); 
    
  // Write the new address book back to disk. 
  fstream output("./log", ios::out | ios::trunc | ios::binary); 
        
  if (!msg1.SerializeToOstream(&output)) { 
      cerr << "Failed to write msg." << endl; 
      return -1; 
  }         
  return 0; 
 }

Msg1 是一個 helloworld 類的對象,set_id() 用來設置 id 的值。SerializeToOstream 將對象序列化後寫入一個 fstream 流。

代碼清單 3 列出了 reader 的主要代碼。

清單 3. Reader
 #include "lm.helloworld.pb.h" 
…
 void ListMsg(const lm::helloworld & msg) { 
  cout << msg.id() << endl; 
  cout << msg.str() << endl; 
 } 
 
 int main(int argc, char* argv[]) { 

  lm::helloworld msg1; 
 
  { 
    fstream input("./log", ios::in | ios::binary); 
    if (!msg1.ParseFromIstream(&input)) { 
      cerr << "Failed to parse address book." << endl; 
      return -1; 
    } 
  } 
 
  ListMsg(msg1); 
  … 
 }

一樣,Reader 聲明類 helloworld 的對象 msg1,而後利用 ParseFromIstream 從一個 fstream 流中讀取信息並反序列化。此後,ListMsg 中採用 get 方法讀取消息的內部信息,並進行打印輸出操做。

運行結果

運行 Writer 和 Reader 的結果以下:

 >writer 
 >reader 
 101 
 Hello

Reader 讀取文件 log 中的序列化信息並打印到屏幕上。本文中全部的例子代碼均可以在附件中下載。您能夠親身體驗一下。

這個例子自己並沒有意義,但只要您稍加修改就能夠將它變成更加有用的程序。好比將磁盤替換爲網絡 socket,那麼就能夠實現基於網絡的數據交換任務。而存儲和交換正是 Protobuf 最有效的應用領域。

 

和其餘相似技術的比較

看完這個簡單的例子以後,但願您已經能理解 Protobuf 能作什麼了,那麼您可能會說,世上還有不少其餘的相似技術啊,好比 XML,JSON,Thrift 等等。和他們相比,Protobuf 有什麼不一樣呢?

簡單說來 Protobuf 的主要優勢就是:簡單,快。

這有測試爲證,項目 thrift-protobuf-compare 比較了這些相似的技術,圖 1 顯示了該項目的一項測試結果,Total Time.

圖 1. 性能測試結果
圖 1. 性能測試結果

Total Time 指一個對象操做的整個時間,包括建立對象,將對象序列化爲內存中的字節序列,而後再反序列化的整個過程。從測試結果能夠看到 Protobuf 的成績很好,感興趣的讀者能夠自行到網站 http://code.google.com/p/thrift-protobuf-compare/wiki/Benchmarking上了解更詳細的測試結果。

Protobuf 的優勢

Protobuf 有如 XML,不過它更小、更快、也更簡單。你能夠定義本身的數據結構,而後使用代碼生成器生成的代碼來讀寫這個數據結構。你甚至能夠在無需從新部署程序的狀況下更新數據結構。只需使用 Protobuf 對數據結構進行一次描述,便可利用各類不一樣語言或從各類不一樣數據流中對你的結構化數據輕鬆讀寫。

它有一個很是棒的特性,即「向後」兼容性好,人們沒必要破壞已部署的、依靠「老」數據格式的程序就能夠對數據結構進行升級。這樣您的程序就能夠沒必要擔憂由於消息結構的改變而形成的大規模的代碼重構或者遷移的問題。由於添加新的消息中的 field 並不會引發已經發布的程序的任何改變。

Protobuf 語義更清晰,無需相似 XML 解析器的東西(由於 Protobuf 編譯器會將 .proto 文件編譯生成對應的數據訪問類以對 Protobuf 數據進行序列化、反序列化操做)。

使用 Protobuf 無需學習複雜的文檔對象模型,Protobuf 的編程模式比較友好,簡單易學,同時它擁有良好的文檔和示例,對於喜歡簡單事物的人們而言,Protobuf 比其餘的技術更加有吸引力。

Protobuf 的不足

Protbuf 與 XML 相比也有不足之處。它功能簡單,沒法用來表示複雜的概念。

XML 已經成爲多種行業標準的編寫工具,Protobuf 只是 Google 公司內部使用的工具,在通用性上還差不少。

因爲文本並不適合用來描述數據結構,因此 Protobuf 也不適合用來對基於文本的標記文檔(如 HTML)建模。另外,因爲 XML 具備某種程度上的自解釋性,它能夠被人直接讀取編輯,在這一點上 Protobuf 不行,它以二進制的方式存儲,除非你有 .proto 定義,不然你無法直接讀出 Protobuf 的任何內容【 2 】。

 

高級應用話題

更復雜的 Message

到這裏爲止,咱們只給出了一個簡單的沒有任何用處的例子。在實際應用中,人們每每須要定義更加複雜的 Message。咱們用「複雜」這個詞,不只僅是指從個數上說有更多的 fields 或者更多類型的 fields,而是指更加複雜的數據結構:

嵌套 Message

嵌套是一個神奇的概念,一旦擁有嵌套能力,消息的表達能力就會很是強大。

代碼清單 4 給出一個嵌套 Message 的例子。

清單 4. 嵌套 Message 的例子
 message Person { 
  required string name = 1; 
  required int32 id = 2;        // Unique ID number for this person. 
  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 phone = 4; 
 }

在 Message Person 中,定義了嵌套消息 PhoneNumber,並用來定義 Person 消息中的 phone 域。這使得人們能夠定義更加複雜的數據結構。

4.1.2 Import Message

在一個 .proto 文件中,還能夠用 Import 關鍵字引入在其餘 .proto 文件中定義的消息,這能夠稱作 Import Message,或者 Dependency Message。

好比下例:

清單 5. 代碼
 import common.header; 

 message youMsg{ 
  required common.info_header header = 1; 
  required string youPrivateData = 2; 
 }

其中 ,common.info_header定義在common.header包內。

Import Message 的用處主要在於提供了方便的代碼管理機制,相似 C 語言中的頭文件。您能夠將一些公用的 Message 定義在一個 package 中,而後在別的 .proto 文件中引入該 package,進而使用其中的消息定義。

Google Protocol Buffer 能夠很好地支持嵌套 Message 和引入 Message,從而讓定義複雜的數據結構的工做變得很是輕鬆愉快。

動態編譯

通常狀況下,使用 Protobuf 的人們都會先寫好 .proto 文件,再用 Protobuf 編譯器生成目標語言所須要的源代碼文件。將這些生成的代碼和應用程序一塊兒編譯。

但是在某且狀況下,人們沒法預先知道 .proto 文件,他們須要動態處理一些未知的 .proto 文件。好比一個通用的消息轉發中間件,它不可能預知須要處理怎樣的消息。這須要動態編譯 .proto 文件,並使用其中的 Message。

Protobuf 提供了 google::protobuf::compiler 包來完成動態編譯的功能。主要的類叫作 importer,定義在 importer.h 中。使用 Importer 很是簡單,下圖展現了與 Import 和其它幾個重要的類的關係。

圖 2. Importer 類
圖 2. Importer 類

Import 類對象中包含三個主要的對象,分別爲處理錯誤的 MultiFileErrorCollector 類,定義 .proto 文件源目錄的 SourceTree 類。

下面仍是經過實例說明這些類的關係和使用吧。

對於給定的 proto 文件,好比 lm.helloworld.proto,在程序中動態編譯它只須要不多的一些代碼。如代碼清單 6 所示。

清單 6. 代碼
 google::protobuf::compiler::MultiFileErrorCollector errorCollector;
 google::protobuf::compiler::DiskSourceTree sourceTree; 

 google::protobuf::compiler::Importer importer(&sourceTree, &errorCollector); 
 sourceTree.MapPath("", protosrc); 

 importer.import(「lm.helloworld.proto」);

首先構造一個 importer 對象。構造函數須要兩個入口參數,一個是 source Tree 對象,該對象指定了存放 .proto 文件的源目錄。第二個參數是一個 error collector 對象,該對象有一個 AddError 方法,用來處理解析 .proto 文件時遇到的語法錯誤。

以後,須要動態編譯一個 .proto 文件時,只需調用 importer 對象的 import 方法。很是簡單。

那麼咱們如何使用動態編譯後的 Message 呢?咱們須要首先了解幾個其餘的類

Package google::protobuf::compiler 中提供瞭如下幾個類,用來表示一個 .proto 文件中定義的 message,以及 Message 中的 field,如圖所示。

圖 3. 各個 Compiler 類之間的關係
圖 3. 各個 Compiler 類之間的關係

類 FileDescriptor 表示一個編譯後的 .proto 文件;類 Descriptor 對應該文件中的一個 Message;類 FieldDescriptor 描述一個 Message 中的一個具體 Field。

好比編譯完 lm.helloworld.proto 以後,能夠經過以下代碼獲得 lm.helloworld.id 的定義:

清單 7. 獲得 lm.helloworld.id 的定義的代碼
 const protobuf::Descriptor *desc = 
    importer_.pool()->FindMessageTypeByName(「lm.helloworld」); 
 const protobuf::FieldDescriptor* field = 
    desc->pool()->FindFileByName (「id」);

經過 Descriptor,FieldDescriptor 的各類方法和屬性,應用程序能夠得到各類關於 Message 定義的信息。好比經過 field->name() 獲得 field 的名字。這樣,您就可使用一個動態定義的消息了。

編寫新的 proto 編譯器

隨 Google Protocol Buffer 源代碼一塊兒發佈的編譯器 protoc 支持 3 種編程語言:C++,java 和 Python。但使用 Google Protocol Buffer 的 Compiler 包,您能夠開發出支持其餘語言的新的編譯器。

類 CommandLineInterface 封裝了 protoc 編譯器的前端,包括命令行參數的解析,proto 文件的編譯等功能。您所須要作的是實現類 CodeGenerator 的派生類,實現諸如代碼生成等後端工做:

程序的大致框架如圖所示:

圖 4. XML 編譯器框圖
圖 4. XML 編譯器框圖

在 main() 函數內,生成 CommandLineInterface 的對象 cli,調用其 RegisterGenerator() 方法將新語言的後端代碼生成器 yourG 對象註冊給 cli 對象。而後調用 cli 的 Run() 方法便可。

這樣生成的編譯器和 protoc 的使用方法相同,接受一樣的命令行參數,cli 將對用戶輸入的 .proto 進行詞法語法等分析工做,最終生成一個語法樹。該樹的結構如圖所示。

圖 5. 語法樹
圖 5. 語法樹

其根節點爲一個 FileDescriptor 對象(請參考「動態編譯」一節),並做爲輸入參數被傳入 yourG 的 Generator() 方法。在這個方法內,您能夠遍歷語法樹,而後生成對應的您所須要的代碼。簡單說來,要想實現一個新的 compiler,您只須要寫一個 main 函數,和一個實現了方法 Generator() 的派生類便可。

在本文的下載附件中,有一個參考例子,將 .proto 文件編譯生成 XML 的 compiler,能夠做爲參考。

 

Protobuf 的更多細節

人們一直在強調,同 XML 相比, Protobuf 的主要優勢在於性能高。它以高效的二進制方式存儲,比 XML 小 3 到 10 倍,快 20 到 100 倍。

對於這些 「小 3 到 10 倍」,「快 20 到 100 倍」的說法,嚴肅的程序員須要一個解釋。所以在本文的最後,讓咱們稍微深刻 Protobuf 的內部實現吧。

有兩項技術保證了採用 Protobuf 的程序能得到相對於 XML 極大的性能提升。

第一點,咱們能夠考察 Protobuf 序列化後的信息內容。您能夠看到 Protocol Buffer 信息的表示很是緊湊,這意味着消息的體積減小,天然須要更少的資源。好比網絡上傳輸的字節數更少,須要的 IO 更少等,從而提升性能。

第二點咱們須要理解 Protobuf 封解包的大體過程,從而理解爲何會比 XML 快不少。

Google Protocol Buffer 的 Encoding

Protobuf 序列化後所生成的二進制消息很是緊湊,這得益於 Protobuf 採用的很是巧妙的 Encoding 方法。

考察消息結構以前,讓我首先要介紹一個叫作 Varint 的術語。

Varint 是一種緊湊的表示數字的方法。它用一個或多個字節來表示一個數字,值越小的數字使用越少的字節數。這能減小用來表示數字的字節數。

好比對於 int32 類型的數字,通常須要 4 個 byte 來表示。可是採用 Varint,對於很小的 int32 類型的數字,則能夠用 1 個 byte 來表示。固然凡事都有好的也有很差的一面,採用 Varint 表示法,大的數字則須要 5 個 byte 來表示。從統計的角度來講,通常不會全部的消息中的數字都是大數,所以大多數狀況下,採用 Varint 後,能夠用更少的字節數來表示數字信息。下面就詳細介紹一下 Varint。

Varint 中的每一個 byte 的最高位 bit 有特殊的含義,若是該位爲 1,表示後續的 byte 也是該數字的一部分,若是該位爲 0,則結束。其餘的 7 個 bit 都用來表示數字。所以小於 128 的數字均可以用一個 byte 表示。大於 128 的數字,好比 300,會用兩個字節來表示:1010 1100 0000 0010

下圖演示了 Google Protocol Buffer 如何解析兩個 bytes。注意到最終計算前將兩個 byte 的位置相互交換過一次,這是由於 Google Protocol Buffer 字節序採用 little-endian 的方式。

圖 6. Varint 編碼
圖 6. Varint 編碼

消息通過序列化後會成爲一個二進制數據流,該流中的數據爲一系列的 Key-Value 對。以下圖所示:

圖 7. Message Buffer
圖 7. Message Buffer

採用這種 Key-Pair 結構無需使用分隔符來分割不一樣的 Field。對於可選的 Field,若是消息中不存在該 field,那麼在最終的 Message Buffer 中就沒有該 field,這些特性都有助於節約消息自己的大小。

以代碼清單 1 中的消息爲例。假設咱們生成以下的一個消息 Test1:

 Test1.id = 10; 
 Test1.str = 「hello」;

則最終的 Message Buffer 中有兩個 Key-Value 對,一個對應消息中的 id;另外一個對應 str。

Key 用來標識具體的 field,在解包的時候,Protocol Buffer 根據 Key 就能夠知道相應的 Value 應該對應於消息中的哪個 field。

Key 的定義以下:

 (field_number << 3) | wire_type

能夠看到 Key 由兩部分組成。第一部分是 field_number,好比消息 lm.helloworld 中 field id 的 field_number 爲 1。第二部分爲 wire_type。表示 Value 的傳輸類型。

Wire Type 可能的類型以下表所示:

表 1. Wire Type
Type Meaning Used For
0 Varint int32, int64, uint32, uint64, sint32, sint64, bool, enum
1 64-bit fixed64, sfixed64, double
2 Length-delimi string, bytes, embedded messages, packed repeated fields
3 Start group Groups (deprecated)
4 End group Groups (deprecated)
5 32-bit fixed32, sfixed32, float

在咱們的例子當中,field id 所採用的數據類型爲 int32,所以對應的 wire type 爲 0。細心的讀者或許會看到在 Type 0 所能表示的數據類型中有 int32 和 sint32 這兩個很是相似的數據類型。Google Protocol Buffer 區別它們的主要意圖也是爲了減小 encoding 後的字節數。

在計算機內,一個負數通常會被表示爲一個很大的整數,由於計算機定義負數的符號位爲數字的最高位。若是採用 Varint 表示一個負數,那麼必定須要 5 個 byte。爲此 Google Protocol Buffer 定義了 sint32 這種類型,採用 zigzag 編碼。

Zigzag 編碼用無符號數來表示有符號數字,正數和負數交錯,這就是 zigzag 這個詞的含義了。

如圖所示:

圖 8. ZigZag 編碼
圖 8. ZigZag 編碼

使用 zigzag 編碼,絕對值小的數字,不管正負均可以採用較少的 byte 來表示,充分利用了 Varint 這種技術。

其餘的數據類型,好比字符串等則採用相似數據庫中的 varchar 的表示方法,即用一個 varint 表示長度,而後將其他部分緊跟在這個長度部分以後便可。

經過以上對 protobuf Encoding 方法的介紹,想必您也已經發現 protobuf 消息的內容小,適於網絡傳輸。假如您對那些有關技術細節的描述缺少耐心和興趣,那麼下面這個簡單而直觀的比較應該能給您更加深入的印象。

對於代碼清單 1 中的消息,用 Protobuf 序列化後的字節序列爲:

 08 65 12 06 48 65 6C 6C 6F 77

而若是用 XML,則相似這樣:

 31 30 31 3C 2F 69 64 3E 3C 6E 61 6D 65 3E 68 65 
 6C 6C 6F 3C 2F 6E 61 6D 65 3E 3C 2F 68 65 6C 6C 
 6F 77 6F 72 6C 64 3E 

一共 55 個字節,這些奇怪的數字須要稍微解釋一下,其含義用 ASCII 表示以下:
 <helloworld> 
    <id>101</id> 
    <name>hello</name> 
 </helloworld>

封解包的速度

首先咱們來了解一下 XML 的封解包過程。XML 須要從文件中讀取出字符串,再轉換爲 XML 文檔對象結構模型。以後,再從 XML 文檔對象結構模型中讀取指定節點的字符串,最後再將這個字符串轉換成指定類型的變量。這個過程很是複雜,其中將 XML 文件轉換爲文檔對象結構模型的過程一般須要完成詞法文法分析等大量消耗 CPU 的複雜計算。

反觀 Protobuf,它只須要簡單地將一個二進制序列,按照指定的格式讀取到 C++ 對應的結構類型中就能夠了。從上一節的描述能夠看到消息的 decoding 過程也能夠經過幾個位移操做組成的表達式計算便可完成。速度很是快。

爲了說明這並非我拍腦殼隨意想出來的說法,下面讓咱們簡單分析一下 Protobuf 解包的代碼流程吧。

以代碼清單 3 中的 Reader 爲例,該程序首先調用 msg1 的 ParseFromIstream 方法,這個方法解析從文件讀入的二進制數據流,並將解析出來的數據賦予 helloworld 類的相應數據成員。

該過程能夠用下圖表示:

圖 9. 解包流程圖
圖 9. 解包流程圖

整個解析過程須要 Protobuf 自己的框架代碼和由 Protobuf 編譯器生成的代碼共同完成。Protobuf 提供了基類 Message 以及 Message_lite 做爲通用的 Framework,,CodedInputStream 類,WireFormatLite 類等提供了對二進制數據的 decode 功能,從 5.1 節的分析來看,Protobuf 的解碼能夠經過幾個簡單的數學運算完成,無需複雜的詞法語法分析,所以 ReadTag() 等方法都很是快。 在這個調用路徑上的其餘類和方法都很是簡單,感興趣的讀者能夠自行閱讀。 相對於 XML 的解析過程,以上的流程圖實在是很是簡單吧?這也就是 Protobuf 效率高的第二個緣由了。

 

結束語

每每瞭解越多,人們就會越以爲本身無知。我惶恐地發現本身居然寫了一篇關於序列化的文章,文中必然有許多想固然而自覺得是的東西,還但願各位可以去僞存真,更但願真的高手能不吝賜教,給我來信。謝謝。

參考資料

學習

討論

相關文章
相關標籤/搜索