protobuf repeated详解

发布时间:2023-05-23

一、repeated的定义以及使用

protobuf中repeated是一种用来表示一个字段可以被重复多次的数据类型,它类似于C++中的vector和Java中的List,可以用来存储一组值。

syntax = "proto3";
message Person {
    string name = 1;
    int32 age = 2;
    repeated string address = 3;
}

在上述代码中,我们定义了一个Person消息对象,其中address是一个repeated类型的属性,表示可以有多个地址。 使用repeated的好处在于它可以在一个字段中存储多个数据,这样可以减少定义属性的个数,提高数据的可效率性。

二、repeated的序列化和反序列化

序列化和反序列化是protobuf最重要的功能之一,可以将消息对象转化为二进制格式,便于网络传输和存储。repeated的序列化和反序列化操作与标量类型基本相同,可以借助protobuf库中的函数实现。 例如,我们需要对上述定义的Person消息对象进行序列化和反序列化操作,可以参考下面的代码:

#include <iostream>
#include <fstream>
#include "person.pb.h" // protobuf生成的头文件
using namespace std;
int main() {
    // 创建一个Person对象,并设置属性值
    tutorial::Person person;
    person.set_name("张三");
    person.set_age(20);
    person.add_address("北京市");
    person.add_address("上海市");
    // 将Person对象序列化为二进制数据
    string buffer;
    person.SerializeToString(&buffer);
    // 将二进制数据反序列化为Person对象
    tutorial::Person newPerson;
    newPerson.ParseFromString(buffer);
    // 输出newPerson对象的属性值
    cout << "姓名:" << newPerson.name() << endl;
    cout << "年龄:" << newPerson.age() << endl;
    cout << "地址:";
    for (int i = 0; i < newPerson.address_size(); i++) {
        cout << newPerson.address(i) << " ";
    }
    cout << endl;
    return 0;
}

可以看到,在上述代码中,我们首先创建了一个Person对象,并设置了属性值,然后通过SerializeToString函数将其序列化为二进制数据。反序列化时,则需要先创建一个空的Person对象,通过ParseFromString函数将二进制数据反序列化为新的对象。

三、repeated的默认值

repeated字段的默认值是一个空列表,该列表包含0个元素。当我们需要添加元素时,可以通过调用add_XXX函数向列表中添加元素。 下面是一个示例代码:

syntax = "proto3";
message Person {
    string name = 1;
    int32 age = 2;
    repeated string address = 3 [default = "北京市"];
}

在上述代码中,我们通过在address字段后加上default选项,指定了address字段的默认值为“北京市”。这意味着,在创建Person消息对象时,如果没有为address属性赋值,那么该属性会默认为“北京市”。当然,我们也可以在创建对象后,通过调用add_address函数向address列表中添加元素。

四、repeated字段的遍历

遍历repeated字段的方式与遍历C++中的vector类似,可以使用for循环来迭代列表中的每一个元素。下面是一个示例代码:

#include <iostream>
#include "person.pb.h"
using namespace std;
int main() {
    // 创建一个Person对象,并向address字段中添加三个元素
    tutorial::Person person;
    person.add_address("北京市");
    person.add_address("上海市");
    person.add_address("广州市");
    // 遍历address字段,并输出每一个元素的值
    for (int i = 0; i < person.address_size(); i++) {
        cout << person.address(i) << endl;
    }
    return 0;
}

五、repeated字段和map的比较

在进行开发时,我们有时需要在消息对象中存储一些键值对数据,这时可以考虑使用map数据类型。与repeated不同,map类型可以直接通过键值访问元素,而无需使用循环遍历。下面是一个示例代码:

syntax = "proto3";
message Person {
    string name = 1;
    int32 age = 2;
    map<string, string> address = 3;
}

在上述代码中,我们使用了map类型来存储地址信息。代码中的map使用了string类型作为键和值的类型。对于map类型的序列化和反序列化操作,与repeated的操作类似,都可以借助protobuf库中提供的函数实现。 但是需要注意的是,由于map类型是一种关联式数据结构,其底层实现依赖于红黑树等算法,因此其性能相对于repeated略低。所以,在实际开发中,我们需要根据具体的需求选择合适的数据类型。