詳解protobuf-從原理到使用


轉自:https://www.jianshu.com/p/419efe983cb2

protobuf是google團隊開發的用於高效存儲和讀取結構化數據的工具。什么是結構化數據呢,正如字面上表達的,就是帶有一定結構的數據。比如電話簿上有很多記錄數據,每條記錄包含姓名、ID、郵件、電話等,這種結構重復出現。

xml、json也可以用來存儲此類結構化數據,但是使用protobuf表示的數據能更加高效,並且將數據壓縮得更小,大約是json格式的1/10,xml格式的1/20。

下面介紹的內容基於protobuf 2.6版本。

1.定義message結構

protobuf將一種結構稱為一個message類型,我們以電話簿中的數據為例。

message Person { required string name = 1; required int32 id = 2; [default = 0] optional string email = 3; repeated int32 samples = 4 [packed=true]; } 

其中Person是message這種結構的名稱,name、id、email是其中的Field,每個Field保存着一種數據類型,后面的1、2、3是Filed對應的數字id。id在115之間編碼只需要占一個字節,包括Filed數據類型和Filed對應數字id,在162047之間編碼需要占兩個字節,所以最常用的數據對應id要盡量小一些。后面具體講到編碼規則時會細講。

Field最前面的required,optional,repeated是這個Filed的規則,分別表示該數據結構中這個Filed有且只有1個,可以是0個或1個,可以是0個或任意個。optional后面可以加default默認值,如果不加,數據類型的默認為0,字符串類型的默認為空串。repeated后面加[packed=true]會使用新的更高效的編碼方式。

注意:使用required規則的時候要謹慎,因為以后結構若發生更改,這個Filed若被刪除的話將可能導致兼容性的問題。

保留Filed和保留Filed number

每個Filed對應唯一的數字id,但是如果該結構在之后的版本中某個Filed刪除了,為了保持向前兼容性,需要將一些id或名稱設置為保留的,即不能被用來定義新的Field。

message Person {
  reserved 2, 15, 9 to 11;
  reserved "samples", "email";
}
枚舉類型

比如電話號碼,只有移動電話、家庭電話、工作電話三種,因此枚舉作為選項,如果沒設置的話枚舉類型的默認值為第一項。在上面的例子中在個人message中加入電話號碼這個Filed。如果枚舉類型中有不同的名字對應相同的數字id,需要加入option allow_alias = true這一項,否則會報錯。枚舉類型中也有reserverd Filed和number,定義和message中一樣。

message Person { required string name = 1; required int32 id = 2; optional string email = 3; enum PhoneType { //allow_alias = true; MOBILE = 0; HOME = 1; WORK = 2; } message PhoneNumber { required string number = 1; optional PhoneType type = 2 [default = HOME]; } repeated PhoneNumber phones = 4; } 
引用其它message類

在同一個文件中,可以直接引用定義過的message類型。

在同一個項目中,可以用import來導入其它message類型。

import "myproject/other_protos.proto"; 

或者在一個message類型中嵌套定義其它的message類型。

message擴展
message Person { // ... extensions 100 to 199; } 

在另一個文件中,import 這個proto之后,可以對Person這個message進行擴展。

extend Person { optional int32 bar = 126; } 

2.數據類型對應關系

在使用規則創建proto類型的數據結構文件之后,會將其轉化成對應編程語言中的頭文件或者類定義。

proto中的數據類型和c++,Python中的數據類型對應規則如下:

.proto C++ Python 介紹
double double float  
float float float  
int32 int32 int 可變長編碼,對負數效率不高
int64 int64 int/long
uint32 uint32 int/long
uint64 uint64 int/long
sint32 int32 int 可變長編碼,對負數效率較高
sint64 int64 int/long
fixed32 uint32 int/long 32位定長編碼
fixed64 uint64 int/long
sfixed32 int32 int
sfixed64 int64 int/long
bool bool bool
string string str/unicode UTF-8編碼或者7-ASCII編碼
bytes string str

3.編碼規則

protobuf有一套高效的數據編碼規則。

可變長整數編碼

每個字節有8bits,其中第一個bit是most significant bit(msb),0表示結束,1表示還要讀接下來的字節。

對message中每個Filed來說,需要編碼它的數據類型、對應id以及具體數據。

數據類型有以下6種,可以用3個bits表示。每個整數編碼用最后3個bits表示數據類型。所以,對應id在1~15之間的Filed,可以用1個字節編碼數據類型、對應id。

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

比如對於下面這個例子來說,如果給a賦值150,那么最終得到的編碼是什么呢?

message Test { optional int32 a = 1; } 

首先數據類型編碼是000,因此和id聯合起來的編碼是00001000. 然后值150的編碼是1 0010110,采用小端序交換位置,即0010110 0000001,前面補1后面補0,即10010110 00000001,即96 01,加上最前面的數據類型編碼字節,總的編碼為08 96 01。

有符號整數編碼

如果用int32來保存一個負數,結果總是有10個字節長度,被看做是一個非常大的無符號整數。使用有符號類型會更高效。它使用一種ZigZag的方式進行編碼。即-1編碼成1,1編碼成2,-2編碼成3這種形式。

也就是說,對於sint32來說,n編碼成 (n << 1) ^ (n >> 31),注意到第二個移位是算法移位。

定長編碼

定長編碼是比較簡單的情況。

4.安裝protobuf包

這里在Mac上下載protobuf 2.6版本記性測試。

$ wget https://github.com/protocolbuffers/protobuf/releases/download/v2.6.1/protobuf-2.6.1.tar.gz $ tar -xvf protobuf-2.6.1.tar.gz $ cd protobuf-2.6.1 $ ./configure $ make -j8 

5.Python測試代碼

1.創建一個addressbook.proto文件如下:

syntax = "proto2"; package tutorial; 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; } 

2.找到src/protoc工具,命令行執行

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

運行完該命令會生成addressbook_pb2.py文件。

3.protobuf的python安裝

$ cd protobuf-2.6.1/python $ python setup.py install # 如果出現報錯package directory 'google/protobuf/compiler' does not exist,則 $ mkdir google/protobuf/compiler 

4.python下基本用法

# encoding:utf-8 import sys import addressbook_pb2 # 獲取類型 address_book = addressbook_pb2.AddressBook() # 添加數據 person = address_book.people.add() # 添加值 person.id = 1234 person.name = "John Doe" person.email = "jdoe@example.com" phone = person.phones.add() phone.number = "555-4321" # enum的數據引用 phone.type = addressbook_pb2.Person.HOME # 檢查是否所有required的Filed都有賦值 print(person.IsInitialized()) # 序列化 res = person.SerializeToString() # 反序列化 a = addressbook_pb2.Person() a.ParseFromString(res) # 從其它message載入,會覆蓋當前的值 b = addressbook_pb2.Person() b.name = "Tom" b.CopyFrom(a) # 清除所有的Filed a.Clear() # 打印出來 print(b) 

6.C++測試代碼

1.同上創建一個addressbook.proto文件。

2.找到src/protoc工具,命令行執行

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

運行完該命令會生成addressbook.pb.h,addressbook.pb.cc文件。

3.protobuf的c++環境安裝

cd protobuf-2.6.1
sudo make install

4.c++下基本用法

參考文獻

[1] https://developers.google.com/protocol-buffers/docs/cpptutorial


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM