本文是對官方文檔的翻譯,而後截取了一篇很是優秀的文章片斷來幫助理解,本人英文水平有限,基本都是直譯,若是有不理解的地方請參考英文官方文檔,參考的文章連接在文章末尾
protocol buffer是google的一個開源項目,它是用於結構化數據串行化的靈活、高效、自動的方法,例如XML,不過它比xml更小、更快、也更簡單。你能夠定義本身的數據結構,而後使用代碼生成器生成的代碼來讀寫這個數據結構。你甚至能夠在無需從新部署程序的狀況下更新數據結構linux
在.proto
文件定義消息,message是.proto
文件最小的邏輯單元,由一系列name-value鍵值對構成。下面的.proto
文件定義了一個"人"的消息:ios
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 phone = 4; }
message消息包含一個或多個編號惟一的字段,每一個字段由字段限制,字段類型,字段名和編號四部分組成,字段限制分爲:optional(可選的)、required(必須的)以及repeated(重複的)。定義好消息後,使用ProtoBuf編譯器生成C++對應的.h
和.cc
文件,源文件提供了message消息的序列化和反序列化等方法:數據庫
# 序列化數據 Person person; person.set_name("John Doe"); person.set_id(1234); person.set_email("jdoe@example.com"); fstream output("myfile", ios::out | ios::binary); person.SerializeToOstream(&output); # 反序列化數據 fstream input("myfile", ios::in | ios::binary); Person person; person.ParseFromIstream(&input);cout << "Name: " << person.name() << endl;cout << "E-mail: " << person.email() << endl;
同XML相比,Protobuf的優點在於高性能,它以高效的二進制存儲方式比XML小3到10倍,快20到100倍,緣由在於:數據結構
Varint 是一種緊湊的表示數字的方法。它用一個或多個字節來表示一個數字,值越小的數字使用越少的字節數。這能減小用來表示數字的字節數。框架
好比對於int32類型的數字,通常須要4個byte來表示,可是採用Varint對於很小的int32類型的數字,則能夠用1個byte來表示。固然凡事都有好的也有很差的一面,採用Varint表示法,大的數字則須要5個byte來表示。從統計的角度來講,通常不會全部的消息中的數字都是大數,所以大多數狀況下,採用Varint後能夠用更少的字節數來表示數字信息。性能
Varint中的每一個byte的最高位bit有特殊的含義,若是該位爲1,表示後續的byte也是該數字的一部分,若是該位爲0則結束,其餘的7個bit都用來表示數字。所以小於128的數字均可以用一個byte表示,大於128的數字會用兩個字節來表示。ui
好比數值300
用Varint來表示就是:1010 1100 0000 0010。下圖演示了Google Protocol Buffer解析Varint表示的300
的過程,因爲Google Protocol Buffer採用小端字節序,因此實際存儲的字節順序是反過來的:google
消息通過序列化後會成爲一個二進制數據流,該流中的數據爲一系列的Key-Value對。以下圖所示:編碼
採用這種Key-Pair結構無需使用分隔符來分割不一樣的 Field。對於可選的Field,若是消息中不存在該Field,那麼在最終的Message Buffer中就沒有該Field,這些特性都有助於節約消息自己的大小。Key 用來標識具體的Field,在解包的時候ProtoBuf根據Key就能夠知道相應的Value應該對應於消息中的哪個Field。Key由字段的編號和字段的線性傳輸類型構成(field_number << 3) | wire_type
翻譯
wire_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 |
使用zigzag編碼,絕對值小的數字,不管正負均可以採用較少的byte來表示,充分利用了Varint這種技術。
消息定義以下:
package lm; message helloworld { required int32 id = 1; // ID required string str = 2; // str optional int32 opt = 3; //optional field }
假設有一條helloworld
消息id=101 str="hello"
,那麼用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></pre>
首先咱們來了解一下XML的封解包過程。XML須要從文件中讀取出字符串,再轉換爲XML文檔對象結構模型。以後再從XML文檔對象結構模型中讀取指定節點的字符串,最後再將這個字符串轉換成指定類型的變量,這個過程很是複雜。其中將XML文件轉換爲文檔對象結構模型的過程一般須要完成詞法文法分析等大量消耗 CPU 的複雜計算。
反觀Protobuf,它只須要簡單地將一個二進制序列按照指定的格式讀取到C++對應的結構類型中就能夠了。從上一節的描述能夠看到,消息的解碼過程也能夠經過幾個位移操做組成的表達式計算便可完成,速度很是快。
上面例子中,Protobuf解包helloworld消息的過程能夠用下圖表示:
整個解析過程須要Protobuf自己的框架代碼和由Protobuf編譯器生成的代碼共同完成。其中Message以及Message_lite做爲通用的流程框架,CodedInputStream、WireFormatLite提供了對二進制數據的解碼功能,並且Protobuf的解碼能夠經過幾個簡單的數學運算完成,無需複雜的詞法語法分析,所以圖中ReadTag()等方法都很是快。相對於XML的解析,整個調用路徑上的其餘類和方法都很是簡單,這也就是ProtoBuf封解包速度迅速的緣由。