詳解protobuf-從原理到使用

protobuf是google團隊開發(fā)的用于高效存儲和讀取結(jié)構(gòu)化數(shù)據(jù)的工具。什么是結(jié)構(gòu)化數(shù)據(jù)呢,正如字面上表達的,就是帶有一定結(jié)構(gòu)的數(shù)據(jù)。比如電話簿上有很多記錄數(shù)據(jù),每條記錄包含姓名、ID、郵件、電話等,這種結(jié)構(gòu)重復(fù)出現(xiàn)。

xml、json也可以用來存儲此類結(jié)構(gòu)化數(shù)據(jù),但是使用protobuf表示的數(shù)據(jù)能更加高效,并且將數(shù)據(jù)壓縮得更小,大約是json格式的1/10,xml格式的1/20。

下面介紹的內(nèi)容基于protobuf 2.6版本。

1.定義message結(jié)構(gòu)

protobuf將一種結(jié)構(gòu)稱為一個message類型,我們以電話簿中的數(shù)據(jù)為例。

message Person {
  required string name = 1;
  required int32 id = 2; [default = 0]
  optional string email = 3;

  repeated int32 samples = 4 [packed=true];

}

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

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

注意:使用required規(guī)則的時候要謹慎,因為以后結(jié)構(gòu)若發(fā)生更改,這個Filed若被刪除的話將可能導(dǎo)致兼容性的問題。

保留Filed和保留Filed number

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

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

比如電話號碼,只有移動電話、家庭電話、工作電話三種,因此枚舉作為選項,如果沒設(shè)置的話枚舉類型的默認值為第一項。在上面的例子中在個人message中加入電話號碼這個Filed。如果枚舉類型中有不同的名字對應(yīng)相同的數(shù)字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來導(dǎo)入其它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.數(shù)據(jù)類型對應(yīng)關(guān)系

在使用規(guī)則創(chuàng)建proto類型的數(shù)據(jù)結(jié)構(gòu)文件之后,會將其轉(zhuǎn)化成對應(yīng)編程語言中的頭文件或者類定義。

proto中的數(shù)據(jù)類型和c++,Python中的數(shù)據(jù)類型對應(yīng)規(guī)則如下:

.proto C++ Python 介紹
double double float
float float float
int32 int32 int 可變長編碼,對負數(shù)效率不高
int64 int64 int/long
uint32 uint32 int/long
uint64 uint64 int/long
sint32 int32 int 可變長編碼,對負數(shù)效率較高
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.編碼規(guī)則

protobuf有一套高效的數(shù)據(jù)編碼規(guī)則。

可變長整數(shù)編碼

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

對message中每個Filed來說,需要編碼它的數(shù)據(jù)類型、對應(yīng)id以及具體數(shù)據(jù)。

數(shù)據(jù)類型有以下6種,可以用3個bits表示。每個整數(shù)編碼用最后3個bits表示數(shù)據(jù)類型。所以,對應(yīng)id在1~15之間的Filed,可以用1個字節(jié)編碼數(shù)據(jù)類型、對應(yīng)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;
}

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

有符號整數(shù)編碼

如果用int32來保存一個負數(shù),結(jié)果總是有10個字節(jié)長度,被看做是一個非常大的無符號整數(shù)。使用有符號類型會更高效。它使用一種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.創(chuàng)建一個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工具,命令行執(zhí)行

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
# 如果出現(xiàn)報錯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()
# 添加數(shù)據(jù)
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的數(shù)據(jù)引用
phone.type = addressbook_pb2.Person.HOME

# 檢查是否所有required的Filed都有賦值
print(person.IsInitialized())

# 序列化
res = person.SerializeToString()
# 反序列化
a = addressbook_pb2.Person()
a.ParseFromString(res)

# 從其它message載入,會覆蓋當(dāng)前的值
b = addressbook_pb2.Person()
b.name = "Tom"
b.CopyFrom(a)

# 清除所有的Filed
a.Clear()
# 打印出來
print(b)

6.C++測試代碼

1.同上創(chuàng)建一個addressbook.proto文件。

2.找到src/protoc工具,命令行執(zhí)行

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

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

3.protobuf的c++環(huán)境安裝

cd protobuf-2.6.1
sudo make install

4.c++下基本用法

參考文獻

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容