google protobuf儲存原理及一些底層api應用
google protobuf
編碼原理
核心基礎是Varints。它用一個或者多個位元組表示一個數字,值越小的數字位元組數越小。
比如要儲存一個int32 型別的數字,通常是4個位元組。但是Varints最少只需要一個位元組就可以了。
怎麼做到呢?
Varints規定小於128的數字都可以用一個位元組來表示,比如10, 它就會用一個位元組 0000 1010 來儲存。
對於大於128的數字,則用更多個位元組儲存。
拿150來舉例子。
150在protobuf的儲存位元組是 1001 0110 0000 0001。
為啥是這個樣子呢?我們一步一步推導。
首先,Varints規定了小於128的數值用1個位元組來表示,大於128用更多個位元組來表示。我們知道一個位元組共8位,最大可以表示的數字是255。但是Varints只用一個位元組表示小於128的數字,換句話說,就是Varints只用了8位中的7位來表示數字,那還有一位被用來幹嘛了呢?
這裡,Varints做了官方規定,每個位元組的最高位是由特殊含義,當最高位為1的時候,代表後續的位元組也是該數字的一部分,後續為0的時候,則表示結束。
因此,對於任意數字的二進位制,我們只能7位,7位的去取。
什麼意思呢?
比如過150,二進位制表示為 1001 0110。
先取後七位 0010110, 作為第一個位元組的內容。
再取餘下的位,補0湊齊7位,就是000 0001。
接著補最高位,這裡要提一句,對於intel機器,是小端位元組序,低位元組位於地址低的。0010110 是低位元組地址,因此排在前面,因為後面的也是數字的一部分,所以高位補1,也就成了10010110。 同樣的,高位元組000 0001,排在後面,並且它後面沒有後續位元組了,所以補0,也就成了 0000 0001。
因此150 在protobuf中的表示方式為 1001 0110 0000 0001。
序列化方式
protobuf 把 message通過一系列key_value對來表示。
Key 的演算法為:
field_number << 3)| wired_type
這裡field_number 就是具體的索引,wired_type的值按下表查詢。
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
5 32-bit fixed32, sfixed32, float
對於int,bool,enum型別,value就是Varint。
而對於string,bytes,message等等型別,value是長度+原始內容編碼。
舉個例子
message Test {
required string a = 2;
}
假如把 a 設定為 “testing”的話, 那麼序列化後的就是
12 07 74 65 73 74 69 64 67
其中12是key。剩下的是value。
怎麼算的呢?先看12, 這裡的12,是個16進位制數字,其二進位制位表示為 0001 0010。
0010 就是型別string的對應的Type值,根據上表,也就是2。
field_number 是 2,也就是0010,左移三位,就成了0001 0000。
按照key的計算公式,和Type值取並後就變成了 0001 0010,即12。
Value是長度加原始內容編碼。
07就是長度, 代表string總長7個位元組。 後面7個數字一次代表每個字母所對應的16進製表示。
對於巢狀型別
message A1{
required int32 a =1:
}
message A2{
required A1 c = 3;
}
如果A2.c.a =150, 序列化後為 1a 03 08 96 01。
1a就是key。 03 08 96 01 就是value。 value是message型別,所以03就是長度,08 96 01就是原始型別編碼。 原始型別是message, 因此 08 就是key, 96 01 就是value了。 96 01 就是150的Varint表示法所對應的16進位制數。
optional 型別,沒有賦值的話,序列化後沒有這個內容。
repeated,會有兩種情況。 如果設定引數packed=true的話,repeated的每個元素都會是一個key-value對,元素就會連續出現。形如 key payload ele1 ele2 ele3。payload指的是後續的總位元組數,比如,
message A
{
repeated int32 a = 1[packed = true];
}
新增3個值,1,2,150。 這個時候payload就是4, 因為150對應的位元組為96 01,2個位元組。
如果不設packed = true。那麼repeated的每個元素都是key-value形式出現,key是一樣的,先後順序標明瞭其在陣列中的順序。
test.proto
message test{
required int32 id = 1;
required string name = 2;
}
#include<google/protobuf/compiler/importer.h>
#include<google/protobuf/dynamic_message.h>
#include<string>
int main()
{
google::protobuf::compiler::DiskSourceTree sourceTree;
sourceTree.MapPath("",".");//後面的.代指當前資料夾。
google::protobuf::compiler::Importer importer(&sourceTree, NULL);//後面一個引數是MyMultiFileErrorCollector型別,測試時可以設定為空
importer.Import("test.proto");//動態編譯test.proto
const google::protobuf::Descriptor *desc = importer.pool()->FindMessageTypeByName("test");//找到test訊息
google::protobuf::DynamicMessageFactory factory;
google::protobuf::Message *message = factory.GetProtoType(desc)->New();//建立了一條message
const google::protobuf::Reflection *ref = message->GetReflection();//拿到反射
const google::protobuf::FieldDescriptor *field = NULL;
field = desc->FindFieldByName("id");
ref->SetInt32(message,field,5);//用反射來更新值
cout << message->DebugString() << endl;//輸出最新的值
}
int main()
{
const google::protobuf::Descriptor *des = google::protobuf::DescriptorPool::generated_pool()->FindMessageType("test");
if (des) {
google::protobuf::Message *proto_type = google::protobuf::MessageFactory()::generated_factory()->GetPrototype(des);
if (proto_type) {
google::protobuf::Message *msg = proto_type->New();
test* s = dynamic_cast<test*>(msg);
s->set_id(5);
s->set_name("zhangsan");
std::string ss;
s->SerializeToString(&ss);
std::cout << ss << std::endl;
test m;
m.ParseFromString(ss);
std::cout << m.id() << " " << m.name() << std::endl;
}
}
}