protobuf


protobuf是由Google开源的一种轻便高效的结构化数据存储格式,与平台无关、语言无关、可扩展、可用于通讯协议和数据存储等领域。与xml和json相比,它序列化后的数据的大小更小、编解码速度更快。

安装

参考:https://github.com/protocolbuffers/protobuf/blob/master/src/README.md

安装完成后,执行如下命令,查看protobuf是否安装成功:

[heql@ubuntu protobuf]$ protoc --version
libprotoc 3.0.0

定义消息类型

当用protobu描述结构化数据时,需要编写.proto文件,一个结构化数据称为message。如下是一个描述Person的message,包含name和age成员:

1
2
3
4
5
6
syntax = "proto3";

message Person {
string name = 1;
int32 age = 2;
}

syntax = "proto3"表示使用proto3语法:如果没有指定这个,编译器会使用proto2。

字段类型

如上面的Person的message,定义了一个string类型的name字段和一个int32类型的age字段。字段可以指定为其他类型,如下表展示了定义于.proto文件中的类型,与之对应的、在自动生成的访问类中定义相应语言的类型。字段的类型也可以为枚举或其他消息类型。

type.png

字段的 id

每个字段都有唯一的一个id。这些id是用来在消息的二进制格式中识别各个字段的,一旦开始使用就不能够再改变。注:[1,15]之内的id在编码的时候会占用一个字节。[16,2047]之内的id则占用2个字节。所以应该为那些频繁出现的消息元素保留[1,15]之内的id。切记:要为将来有可能添加的、频繁出现的id预留一些id。

最小的id可以从1开始,最大到2^29 - 1 or 536,870,911。不可以使用其中的[19000-19999] (从FieldDescriptor::kFirstReservedNumberFieldDescriptor::kLastReservedNumber) 的id, Protobuf协议实现中对这些进行了预留。如果非要在.proto文件中使用这些预留id,编译时就会报警。同样你也不能使用早期保留的id。

指定字段规则

proto3 所指定的消息字段修饰符必须是如下之一:

  • singular:消息格式中该字段可以有0个或1个值(不超过1个)
  • repeated: 在一个格式良好的消息中,这种字段可以重复任意多次(包括0次),重复的值的顺序会被保留

如果字段中没有指定修饰符,则默认为singular,如上面的name、age字段。

编译 .proto 文件

写好proto文件之后就可以用 Protobuf 编译器将该文件编译成目标语言了,如下命令:

protoc --cpp_out=. person.proto

--cpp_out=. 表示生成的目标的语言为C++,生成的文件放在当前目录下。其他语言类似,如--python_out,表示生成python代码。

运行上面的命令,则会生成两个文件:

  • person.pb.h:定义了 C++ 类的头文件
  • person.pb.cc:C++ 类的实现文件

在生成的头文件中,定义了一个C++类Person,这个类提供了对消息进行操作的接口。如下是对生成Person类定义的常用方法的说明性注释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
class Person : public ::google::protobuf::Message {
public:
Person();
virtual ~Person();

Person(const Person& from);

inline Person& operator=(const Person& from) {
CopyFrom(from);
return *this;
}

// implements Message ----------------------------------------------

// 创建一个新的Person对象,等同于clone
inline Person* New() const { return New(NULL); }

// 用另外一个Person对象初始化当前对象,等同于赋值操作符重载(operator=)
void CopyFrom(const Person& from);
// 清空当前对象中的所有数据
void Clear();
// 判断当前状态是否已经初始化
bool IsInitialized() const;

// 在给当前对象的所有变量赋值之后,获取该对象序列化后所需要的字节数
int ByteSize() const;


// accessors -------------------------------------------------------

// 下面的成员函数都是因message中定义的name字段而生成
// optional string name = 1;
void clear_name();
static const int kNameFieldNumber = 1;
// 返回name字段的当前值
const ::std::string& name() const;
// 设置name字段的值
void set_name(const ::std::string& value);
void set_name(const char* value);
void set_name(const char* value, size_t size);
// 返回name字段对象的指针
::std::string* mutable_name();
// 释放当前对象对name字段的所有权,同时返回name字段对象指针
// 调用此函数之后,name字段对象的所有权将移交给调用者
::std::string* release_name();

// 下面的成员函数都是因message中定义的age字段而生成
// optional int32 age = 2;
void clear_age();
static const int kAgeFieldNumber = 2;
::google::protobuf::int32 age() const;
void set_age(::google::protobuf::int32 value);
};

使用 protobuf

编译生成了对应的目标代码后,就可以使用相应的接口函数,操作message的字段,如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include "person.pb.h"

#include <stdio.h>

int main(int argc, char* argv[]) {
Person p1;

p1.set_name("lisa");
p1.set_age(18);

int length = p1.ByteSize();
char* buf = new char[length];
if (p1.SerializeToArray(buf, length)) {
Person p2;
p2.ParseFromArray(buf, length);
printf("name = %s, age = %d\n", p2.name().c_str(), p2.age());
}
delete []buf;
}

上面的代码,在为对象p1的字段设置完值后,把p1序列化到动态内存中,对象p2再从动态内存中反序列化回来。

编译运行程序,输出结果:

[heql@ubuntu protobuf]$ g++ person.cc person.pb.cc -lprotobuf
[heql@ubuntu protobuf]$ ./a.out 
name = lisa, age = 18

除了序列化到动态内存中,protobuf还提供其他序列化接口,如:SerializeToString、SerializeToCodedStream序列化到文件等接口。如下代码是把对象p序列化到person.bin文件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include "person.pb.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

using std::string;

int main(int argc, char* argv[]) {
Person p;

p.set_name("lisa");
p.set_age(18);

int fd;
if ((fd = open("person.bin", O_RDWR | O_CREAT, 0666)) < 0) {
return -1;
}
p.SerializeToFileDescriptor(fd);
close(fd);

return 0;
}

下面的代码是从person.bin文件,反序列化对象p

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include "person.pb.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char* argv[]) {
Person p;

int fd;
if ((fd = open("person.bin", O_RDONLY)) < 0) {
return -1;
}

if (p.ParseFromFileDescriptor(fd)) {
printf("name = %s, age = %d\n", p.name().c_str(), p.age());
}
close(fd);

return 0;
}

编译运行程序,输出结果:

[heql@ubuntu protobuf]$ g++ writer.cc person.pb.cc -o writer -lprotobuf
[heql@ubuntu protobuf]$ g++ reader.cc person.pb.cc -o reader -lprotobuf
[heql@ubuntu protobuf]$ ./writer 
[heql@ubuntu protobuf]$ ./reader 
name = lisa, age = 18

repeated 修饰的字段

repeated修饰的字段,表示这个字段可以重复任意多次(包括0次),重复的值的顺序会被保留。如下面的代码为Person添加一个repeated的email字段:

1
2
3
4
5
6
7
syntax = "proto3";

message Person {
string name = 1;
int32 age = 2;
repeated string email = 3;
}

编译proto文件,生成的C++代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Person : public ::google::protobuf::Message {
public:

// repeated string email = 3;
// 返回数组的大小
int email_size() const;
// 清空数组
void clear_email();
static const int kEmailFieldNumber = 3;
// 根据下标,返回对应的元素
const ::std::string& email(int index) const;
// 根据下标,返回对应的元素的指针
::std::string* mutable_email(int index);
// 根据下标,设置对应的元素的值
void set_email(int index, const ::std::string& value);
void set_email(int index, const char* value);
void set_email(int index, const char* value, size_t size);
// 返回一个新增元素的指针,可对该指针直接赋值
::std::string* add_email();
// 向数组中添加一个新元素
void add_email(const ::std::string& value);
void add_email(const char* value);
void add_email(const char* value, size_t size);
// 获取email字段所表示的容器,该函数返回的容器仅用于遍历并读取,不能直接修改。
const ::google::protobuf::RepeatedPtrField< ::std::string>& email() const;
// 获取email字段所表示的容器指针,该函数返回的容器指针可用于遍历和直接修改。
::google::protobuf::RepeatedPtrField< ::std::string>* mutable_email();
};

访问email生成的接口函数,如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include "person.pb.h"

#include <stdio.h>

int main(int argc, char* argv[]) {
Person p1;

p1.set_name("lisa");
p1.set_age(18);
p1.add_email("test@gmail.com");
p1.add_email("test@qq.com");
p1.add_email("test@163.com");

int length = p1.ByteSize();
char* buf = new char[length];
if (p1.SerializeToArray(buf, length)) {
Person p2;
p2.ParseFromArray(buf, length);
printf("name = %s, age = %d\n", p2.name().c_str(), p2.age());

const ::google::protobuf::RepeatedPtrField< ::std::string>& email = p2.email();
::google::protobuf::RepeatedPtrField< ::std::string>::const_iterator iter = email.begin();
for (; iter != email.end(); iter++) {
printf("email = %s\n", iter->c_str());
}
}
delete []buf;
}

编译运行程序,输出结果:

[heql@ubuntu protobuf]$ g++ person.cc person.pb.cc -lprotobuf
[heql@ubuntu protobuf]$ ./a.out 
name = lisa, age = 18
email = test@gmail.com
email = test@qq.com
email = test@163.com

枚举类型

如下面的代码为Person添加一个枚举类型的gender字段:

1
2
3
4
5
6
7
8
9
10
11
12
13
syntax = "proto3";

message Person {
string name = 1;
int32 age = 2;
repeated string email = 3;

enum Gender {
MALE = 0;
FEMALE = 1;
}
Gender gender = 4
}

编译proto文件,生成的枚举类型的gender字段的C++代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
enum Person_Gender {
Person_Gender_MALE = 0,
Person_Gender_FEMALE = 1,
Person_Gender_Person_Gender_INT_MIN_SENTINEL_DO_NOT_USE_ = ::google::protobuf::kint32min,
Person_Gender_Person_Gender_INT_MAX_SENTINEL_DO_NOT_USE_ = ::google::protobuf::kint32max
};
bool Person_Gender_IsValid(int value);
const Person_Gender Person_Gender_Gender_MIN = Person_Gender_MALE;
const Person_Gender Person_Gender_Gender_MAX = Person_Gender_FEMALE;
const int Person_Gender_Gender_ARRAYSIZE = Person_Gender_Gender_MAX + 1;

class Person : public ::google::protobuf::Message {
public:

typedef Person_Gender Gender;
static const Gender MALE =
Person_Gender_MALE;
static const Gender FEMALE =
Person_Gender_FEMALE;
static inline bool Gender_IsValid(int value) {
return Person_Gender_IsValid(value);
}
static const Gender Gender_MIN =
Person_Gender_Gender_MIN;
static const Gender Gender_MAX =
Person_Gender_Gender_MAX;
static const int Gender_ARRAYSIZE =
Person_Gender_Gender_ARRAYSIZE;

// optional .Person.Gender gender = 4;
void clear_gender();
static const int kGenderFieldNumber = 4;
::Person_Gender gender() const;
void set_gender(::Person_Gender value);
};

访问gender生成的接口函数,如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include "person.pb.h"

#include <stdio.h>

int main(int argc, char* argv[]) {
Person p1;

p1.set_name("lisa");
p1.set_age(18);
p1.add_email("test@gmail.com");
p1.add_email("test@qq.com");
p1.add_email("test@163.com");
p1.set_gender(Person_Gender_FEMALE);

int length = p1.ByteSize();
char* buf = new char[length];
if (p1.SerializeToArray(buf, length)) {
Person p2;
p2.ParseFromArray(buf, length);
printf("name = %s, age = %d\n", p2.name().c_str(), p2.age());

const ::google::protobuf::RepeatedPtrField< ::std::string>& email = p2.email();
::google::protobuf::RepeatedPtrField< ::std::string>::const_iterator iter = email.begin();
for (; iter != email.end(); iter++) {
printf("email = %s\n", iter->c_str());
}
printf("male = %d\n", p2.gender());
}
delete []buf;
}

编译运行程序,输出结果:

[heql@ubuntu protobuf]$ g++ person.cc person.pb.cc -lprotobuf
[heql@ubuntu protobuf]$ ./a.out 
name = lisa, age = 18
email = test@gmail.com
email = test@qq.com
email = test@163.com
male = 1

默认值

在proto3中,如果没有为相应的字段的赋值,则对应的字段使用默认值:

  • string,默认是一个空string
  • 对于bytes,默认是一个空的bytes
  • 对于bool,默认是false
  • 对于数值类型,默认是0
  • 对于枚举,默认是第一个定义的枚举值,必须为0
  • 对于消息类型(message),域没有被设置,确切的消息是根据语言确定的
  • repeated的字段默认是null, 空的列表

如下代码,Person使用默认值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include "person.pb.h"

#include <stdio.h>

int main(int argc, char* argv[]) {
Person p1;

int length = p1.ByteSize();
char* buf = new char[length];
if (p1.SerializeToArray(buf, length)) {
Person p2;
p2.ParseFromArray(buf, length);
printf("name = %s, age = %d\n", p2.name().c_str(), p2.age());
printf("email_size = %d\n", p2.email_size());
printf("male = %d\n", p2.gender());
}
delete []buf;
}

编译运行程序,输出结果:

[heql@ubuntu protobuf]$ g++ person.cc person.pb.cc -lprotobuf
[heql@ubuntu protobuf]$ ./a.out 
name = , age = 0
email_size = 0
male = 0

嵌套类型

可以在其他消息类型中定义、使用消息类型,下面的代码,在Peron消息中定义了一个Address消息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
syntax = "proto3";

message Person {
string name = 1;
int32 age = 2;
repeated string email = 3;

enum Gender {
MALE = 0;
FEMALE = 1;
}
Gender gender = 4;

message Address {
string country = 1;
string city = 2;
}
Address address = 5;
}

编译proto文件,会生成Person_Address类和Person类,Person_Address类生成字段country和city的访问函数,Person类生成Person_Address类的访问函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class Person_Address : public ::google::protobuf::Message {
public:

// optional string country = 1;
void clear_country();
static const int kCountryFieldNumber = 1;
const ::std::string& country() const;
void set_country(const ::std::string& value);
void set_country(const char* value);
void set_country(const char* value, size_t size);
::std::string* mutable_country();
::std::string* release_country();
void set_allocated_country(::std::string* country);

// optional string city = 2;
void clear_city();
static const int kCityFieldNumber = 2;
const ::std::string& city() const;
void set_city(const ::std::string& value);
void set_city(const char* value);
void set_city(const char* value, size_t size);
::std::string* mutable_city();
::std::string* release_city();
void set_allocated_city(::std::string* city);
};


class Person : public ::google::protobuf::Message {
public:

typedef Person_Address Address;

// optional .Person.Address address = 5;
bool has_address() const;
void clear_address();
static const int kAddressFieldNumber = 5;
const ::Person_Address& address() const;
::Person_Address* mutable_address();
::Person_Address* release_address();
void set_allocated_address(::Person_Address* address);
};

访问Address生成的接口函数,如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include "person.pb.h"

#include <stdio.h>

int main(int argc, char* argv[]) {
Person p1;

p1.set_name("lisa");
p1.set_age(18);
p1.add_email("test@gmail.com");
p1.add_email("test@qq.com");
p1.add_email("test@163.com");
p1.set_gender(Person_Gender_FEMALE);
Person_Address* address = p1.mutable_address();
address->set_country("China");
address->set_city("GuangZhou");

int length = p1.ByteSize();
char* buf = new char[length];
if (p1.SerializeToArray(buf, length)) {
Person p2;
p2.ParseFromArray(buf, length);
printf("name = %s, age = %d\n", p2.name().c_str(), p2.age());

const ::google::protobuf::RepeatedPtrField< ::std::string>& email = p2.email();
::google::protobuf::RepeatedPtrField< ::std::string>::const_iterator iter = email.begin();
for (; iter != email.end(); iter++) {
printf("email = %s\n", iter->c_str());
}
printf("male = %d\n", p2.gender());
printf("address:\n");
printf(" country = %s, city = %s\n", p2.address().country().c_str(), p2.address().city().c_str());
}
delete []buf;
}

编译运行程序,输出结果:

[heql@ubuntu protobuf]$ g++ person.cc person.pb.cc -lprotobuf
[heql@ubuntu protobuf]$ ./a.out
name = lisa, age = 18
email = test@gmail.com
email = test@qq.com
email = test@163.com
male = 1
address:
    country = China, city = GuangZhou

repeated 嵌套类型

在Peron消息中定义了一个repeated修饰的PhoneNumber消息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
syntax = "proto3";

message Person {
string name = 1;
int32 age = 2;
repeated string email = 3;

enum Gender {
MALE = 0;
FEMALE = 1;
}
Gender gender = 4;

message Address {
string country = 1;
string city = 2;
}
Address address = 5;

message PhoneNumber {
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
string number = 1;
PhoneType type = 2;
}
repeated PhoneNumber phone_number = 6;
}

编译proto文件,会生成Person_PhoneNumber类,Person_PhoneNumber类生成字段number和type的访问函数,Person类生成Person_PhoneNumber类的访问函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class Person_PhoneNumber : public ::google::protobuf::Message {
public:

// optional string number = 1;
void clear_number();
static const int kNumberFieldNumber = 1;
const ::std::string& number() const;
void set_number(const ::std::string& value);
void set_number(const char* value);
void set_number(const char* value, size_t size);
::std::string* mutable_number();
::std::string* release_number();
void set_allocated_number(::std::string* number);

// optional .Person.PhoneNumber.PhoneType type = 2;
void clear_type();
static const int kTypeFieldNumber = 2;
::Person_PhoneNumber_PhoneType type() const;
void set_type(::Person_PhoneNumber_PhoneType value);
};


class Person : public ::google::protobuf::Message {
public:

// repeated .Person.PhoneNumber phone_number = 6;
int phone_number_size() const;
void clear_phone_number();
static const int kPhoneNumberFieldNumber = 6;
const ::Person_PhoneNumber& phone_number(int index) const;
::Person_PhoneNumber* mutable_phone_number(int index);
::Person_PhoneNumber* add_phone_number();
::google::protobuf::RepeatedPtrField< ::Person_PhoneNumber >*
mutable_phone_number();
const ::google::protobuf::RepeatedPtrField< ::Person_PhoneNumber >&
phone_number() const;
};

访问phone_number生成的接口函数,如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include "person.pb.h"

#include <stdio.h>

int main(int argc, char* argv[]) {
Person p1;

p1.set_name("lisa");
p1.set_age(18);
p1.add_email("test@gmail.com");
p1.add_email("test@qq.com");
p1.add_email("test@163.com");
p1.set_gender(Person_Gender_FEMALE);
Person_Address* address = p1.mutable_address();
address->set_country("China");
address->set_city("GuangZhou");

struct PhoneNumber {
Person_PhoneNumber_PhoneType type;
std::string number;
};

PhoneNumber phoneNumber[] = {
{Person_PhoneNumber::MOBILE, "136********"},
{Person_PhoneNumber::HOME, "020-*******"},
{Person_PhoneNumber::WORK, "159********"}
};

for (size_t i = 0; i < sizeof(phoneNumber)/sizeof(phoneNumber[0]); i++) {
::Person_PhoneNumber* phone_number = p1.add_phone_number();
phone_number->set_type(phoneNumber[i].type);
phone_number->set_number(phoneNumber[i].number);
}

int length = p1.ByteSize();
char* buf = new char[length];
if (p1.SerializeToArray(buf, length)) {
Person p2;
p2.ParseFromArray(buf, length);
printf("name = %s, age = %d\n", p2.name().c_str(), p2.age());

const ::google::protobuf::RepeatedPtrField< ::std::string>& email = p2.email();
::google::protobuf::RepeatedPtrField< ::std::string>::const_iterator iter = email.begin();
for (; iter != email.end(); iter++) {
printf("email = %s\n", iter->c_str());
}
printf("male = %d\n", p2.gender());
printf("address:\n");
printf(" country = %s, city = %s\n", p2.address().country().c_str(), p2.address().city().c_str());

printf("phone:\n");
for (size_t i = 0; i < p2.phone_number_size(); i++) {
printf(" type = %d, number = %s\n", p2.phone_number(i).type(), p2.phone_number(i).number().c_str());
}
}
delete []buf;
}

编译运行程序,输出结果:

[heql@ubuntu protobuf]$ g++ person.cc person.pb.cc -lprotobuf
[heql@ubuntu protobuf]$ ./a.out
name = lisa, age = 18
email = test@gmail.com
email = test@qq.com
email = test@163.com
male = 1
address:
    country = China, city = GuangZhou
phone:
    type = 0, number = 136********
    type = 1, number = 020-*******
    type = 2, number = 159********

遍历repeated字段时,也可以使用的迭代器进行遍历,如下代码:

1
2
3
4
5
6
printf("phone:\n");
::google::protobuf::RepeatedPtrField< ::Person_PhoneNumber >* phone_number = p2.mutable_phone_number();
::google::protobuf::RepeatedPtrField< ::Person_PhoneNumber >::iterator it = phone_number->begin();
for (; it != phone_number->end(); it++) {
printf(" type = %d, number = %s\n", it->type(), it->number().c_str());
}

导入定义

可以通过导入其他.proto文件中的定义来使用它们。要导入其他.proto文件的定义,需要在你的文件中添加一个导入声明,如下面,是在AddressBook.proto文件中导入Person.proto:

1
2
3
4
5
6
7
syntax = "proto3";

import "person.proto";

message AddressBook {
Person person = 1;
}

保留标识符(Reserved)

如果你通过删除或者注释所有域,以后的用户在更新这个类型的时候可能重用这些标识号。如果你使用旧版本加载相同的.proto文件会导致严重的问题,包括数据损坏、隐私错误等等。现在有一种确保不会发生这种情况的方法就是为字段tag指定reserved标识符,protocol buffer的编译器会警告未来尝试使用这些域标识符的用户。

1
2
3
4
message Foo {
reserved 2, 15, 9 to 11;
reserved "foo", "bar";
}

Map 类型

在proto3支持Map类型,其中key_type可以是任意Integer或者string类型(所以,除了floating和bytes的任意标量类型都是可以的)value_type可以是任意类型。

  • Map字段不能是repeated
  • 序列化后的顺序和map迭代器的顺序是不确定的
  • 当为.proto文件产生生成文本格式的时候,map会按照key 的顺序排序,数值化的key会按照数值排序
  • 不支持重复的key

如下代码,把上面的PhoneNumber字段改成用Map类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
syntax = "proto3";

message Person {
string name = 1;
int32 age = 2;
repeated string email = 3;

enum Gender {
MALE = 0;
FEMALE = 1;
}
Gender gender = 4;

message Address {
string country = 1;
string city = 2;
}
Address address = 5;

enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
map<int32, string> phone_number = 6;
}

编译proto文件,生成Map类型的访问函数:

1
2
3
4
5
6
7
8
9
10
11
12
class Person : public ::google::protobuf::Message {
public:

// map<int32, string> phone_number = 6;
int phone_number_size() const;
void clear_phone_number();
static const int kPhoneNumberFieldNumber = 6;
const ::google::protobuf::Map< ::google::protobuf::int32, ::std::string >&
phone_number() const;
::google::protobuf::Map< ::google::protobuf::int32, ::std::string >*
mutable_phone_number();
};

访问Map类型生成的接口函数,如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include "person.pb.h"

#include <stdio.h>

int main(int argc, char* argv[]) {
Person p1;

p1.set_name("lisa");
p1.set_age(18);
p1.add_email("test@gmail.com");
p1.add_email("test@qq.com");
p1.add_email("test@163.com");
p1.set_gender(Person_Gender_FEMALE);
Person_Address* address = p1.mutable_address();
address->set_country("China");
address->set_city("GuangZhou");

::google::protobuf::Map< ::google::protobuf::int32, ::std::string >* phone_number = p1.mutable_phone_number();

phone_number->insert(google::protobuf::MapPair<int32_t, std::string>(Person::MOBILE, "136********"));
phone_number->insert(google::protobuf::MapPair<int32_t, std::string>(Person::MOBILE, "136********"));
phone_number->insert(google::protobuf::MapPair<int32_t, std::string>(Person::HOME, "020-*******"));
phone_number->insert(google::protobuf::MapPair<int32_t, std::string>(Person::HOME, "020-*******"));
phone_number->insert(google::protobuf::MapPair<int32_t, std::string>(Person::WORK, "159********"));
phone_number->insert(google::protobuf::MapPair<int32_t, std::string>(Person::WORK, "159********"));


int length = p1.ByteSize();
char* buf = new char[length];
if (p1.SerializeToArray(buf, length)) {
Person p2;
p2.ParseFromArray(buf, length);
printf("name = %s, age = %d\n", p2.name().c_str(), p2.age());

const ::google::protobuf::RepeatedPtrField< ::std::string>& email = p2.email();
::google::protobuf::RepeatedPtrField< ::std::string>::const_iterator iter = email.begin();
for (; iter != email.end(); iter++) {
printf("email = %s\n", iter->c_str());
}
printf("male = %d\n", p2.gender());
printf("address:\n");
printf(" country = %s, city = %s\n", p2.address().country().c_str(), p2.address().city().c_str());

printf("phone:\n");
::google::protobuf::Map< ::google::protobuf::int32, ::std::string >* phone_number = p2.mutable_phone_number();
::google::protobuf::Map< ::google::protobuf::int32, ::std::string >::iterator it = phone_number->begin();

for (; it != phone_number->end(); it++) {
printf(" type = %d, number = %s\n", it->first, it->second.c_str());
}
}
delete []buf;
}

编译运行程序,输出结果:

[heql@ubuntu protobuf]$ g++ person.cc person.pb.cc -lprotobuf
[heql@ubuntu protobuf]$ ./a.out
name = lisa, age = 18
email = test@gmail.com
email = test@qq.com
email = test@163.com
male = 1
address:
    country = China, city = GuangZhou
phone:
    type = 0, number = 136********
    type = 1, number = 020-*******
    type = 2, number = 159********

其他

在proto3中还支持Any类型、Oneof特性等。详情可以参考官方文档