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;
        }
    } 

}

猜你喜欢

转载自blog.csdn.net/jxhaha/article/details/79897933