Google开发的一套对数据结构进行序列化的方法第一篇:protobuf概要总结

1、protobuf支持的数据类型

  1. 基本数据类型:bool、int32、int64、uint32、uint64、float、double、string、bytes。
  2. 枚举类型:使用关键字enum定义。
  3. 消息类型:使用关键字message定义。
  4. Oneof类型:表示这些字段中只能有一个字段被设置了值。
  5. Map类型:表示key-value映射关系的数据类型。
  6. Any类型:表示任意类型的数据。
  7. Duration类型:表示时间间隔。
  8. Timestamp类型:表示时间戳。

Protobuf中的Map类型是一种特殊的数据类型,可以将一个键**(key)映射到一个值(value),其中keyvalue可以是任意数据类型,但key必须是基本数据类型。Map类型使用关键字map**定义,示例代码如下:

map<string, int32> my_map = 1;

上述代码定义了一个名为my_mapmap类型,其中键为string类型,值为int32类型。Map类型还支持嵌套,即可以将map类型作为值类型,示例代码如下:

map<string, map<string, int32>> my_map = 1;

Protobuf中还支持数组类型,即repeated关键字定义的重复字段。示例代码如下:

repeated int32 my_array = 1;

上述代码定义了一个名为my_arrayint32类型的数组。数组中的元素可以重复出现,元素的顺序与添加的顺序相同。数组也可以嵌套,示例代码如下:

repeated map<string, int32> my_array = 1;

2、protobuf中获取map类型

C++中,使用Protobuf获取Map类型和数组类型的值时,也需要使用号。原因是,在生成的C++代码中,Map类型和数组类型的字段被封装在一个类似于指针的结构体中,使用 *号可以获取到这个结构体指针,从而访问map*类型和数组类型的值。

对于map类型的字段,生成的C++代码会自动生成一个类似于指针的结构体,结构体中包含了map类型的键值对。例如,如果Protobuf定义了一个名为my_mapmap类型字段,那么在生成的C++代码中就会有一个名为MyMessage::MyMap* MyMap_的结构体指针,可以通过该指针访问my_map中的键值对。示例代码如下:

syntax="proto3";
package Person;

message MyMessage{
	map<string,int32> my_map;
}

在**C++**代码中可以这样赋值和获取值:

MyMessage message;
(*message.mutable_my_map())["key1"] = 1;
(*message.mutable_my_map())["key2"] = 2;
(*message.mutable_my_map())["key3"] = 3;
int value = (*message.mutable_my_map())["key2"];

需要注意的是,C++中使用号获取map*类型和数组类型的值时,需要进行空值判断,以避免访问空指针或越界访问的错误。

 Map<Key, T>* MutableMap() override {
    MapFieldBase::SyncMapWithRepeatedField();
    Map<Key, T>* result = impl_.MutableMap();
    MapFieldBase::SetMapDirty();
    return result;
  }

2.1、protobuf中的嵌套类型repeated map<string, int32> my_array = 1;

C++中,使用Protobuf可以定义嵌套类型,即在消息中定义的字段类型是其他消息或其他消息的列表或map类型。例如,可以在消息中定义一个名为my_array的嵌套字段,该字段类型为一个map类型的列表,示例代码如下:

syntax="proto3";

message MyMessage {  
	repeated map<string, int32> my_array = 1;
}

上述代码定义了一个名为MyMessage的消息类型,该消息中包含了一个名为my_array数组的字段,该数组字段类型为一个map类型的列表。其中,repeated关键字表示该字段是一个列表数组类型,map<string, int32>表示该列表中的元素是一个键值对,其中键是string类型,值是int32类型。

使用上述定义的MyMessage消息,可以创建一个包含多个map类型的列表的实例,并将其序列化为二进制格式。示例代码如下:

MyMessage message;
std::map<std::string, int32_t> map1{{"key1", 1}, {"key2", 2}};
std::map<std::string, int32_t> map2{{"key3", 3}, {"key4", 4}};
*message.add_my_array() = map1;
*message.add_my_array() = map2;
std::string serialized_message = message.SerializeAsString();

上述代码创建了一个MyMessage类型的实例message,并向其my_array字段中添加了两个map类型的元素map1map2,然后将message序列化为二进制格式。在反序列化时,可以将二进制数据反序列化为MyMessage类型的实例,并访问其中的my_array字段,示例代码如下:

MyMessage new_message;
new_message.ParseFromString(serialized_message);
for (const auto& map : new_message.my_array()) {  
	for (const auto& kv : map) {    
		std::cout << kv.first << ":" << kv.second << " ";  
	}  
	std::cout << std::endl;
}

上述代码将二进制数据反序列化为MyMessage类型的实例new_message,并遍历其中的my_array字段。对于my_array中的每个元素(即一个map类型的键值对),遍历其中的键值对,并输出其键和值。

3、protobuf中获取数组类型

对于数组类型的字段,生成的C++代码会自动生成一个类似于指针的结构体,结构体中包含了数组元素。例如,如果Protobuf定义了一个名为my_arrayint32类型的数组,那么在生成的C++代码中就会有一个名为MyMessage::MyArray* MyArray_的结构体指针,可以通过该指针访问my_array中的元素。示例代码如下:

syntax="proto3";
package=Person;

message MyMessage{
	repeated int32 my_array;
}

在**C++**代码中可以这样赋值和获取值:

MyMessage message;
message.add_my_array(1);
message.add_my_array(2);
message.add_my_array(3);
int value = message.my_array(1);

4、为什么 proto3 移除了 required 和 optional

syntax="proto3"

我们删除了 proto3 中的 required 字段,因为 required 字段通常被认为是有害的并且违反了 protobuf 的兼容性语义。

使用 protobuf 的整个想法是,它允许您添加/删除协议定义中的字段,同时仍然完全向前/向后兼容较新/较旧的二进制文件。

required 字段打破了这一点。

您永远不能安全地向 .proto 定义添加 required 字段,也不能安全地删除现有的 required 字段,因为这两个操作都会破坏 wire 兼容性。

例如,如果向 .proto 定义添加 required 字段,则使用旧定义构建的二进制文件将无法解析使用旧定义序列化的数据,因为旧数据中不存在 required 字段。

在一个复杂的系统中,.proto 定义在系统的许多不同组件中广泛共享,添加/删除 required 字段可以轻松地降低系统的多个部分。

我们已经多次看到由此造成的生产问题,并且 Google 内部几乎禁止任何人添加/删除 required 字段。

出于这个原因,我们完全删除了 proto3 中的 required 字段。

  • required:表示该字段必须有值,不能为空,否则message被认为是未初始化的。如果试图build一个未初始化的message将会抛出RuntimeException。解析未初始化的message会抛出IOException

  • optional:表示该段为可选值,可以不进行设置。如果不设置,会设置一个默认值。可以设置一个默认值,正如示例中的type字段。否则使用系统默认值,数字类型默认为0;字符串类型默认为空字符串;逻辑类型默认为false;内部自定义的message,默认值通常是message的实例或原型。

  • **repeated:**表示该字段可以重复,可等同于动态数组。

注意:使用required字段一定要小心,因为该字段是永久性的。如果以后因为某种原因,想不用该字段,或者要将该字段改成optional或者repeated,那么使用旧接口读取新的协议时,如果发现没有该字段,他们会认为该字段是不完整的,会拒接接收该消息,或者直接丢弃。

5、protobuf数据格式对比xml数据格式

protobuf是由Google开发的一套对数据结构进行序列化的方法,可用做通信协议,数据存储格式,等等。其特点是不限语言、不限平台、扩展性强,就像XML一样。与xml相比,protobuf有以下特点:

5.1、操作更简单

例如,我们要定义一个个人信息的结构,其中包括名称和邮箱地址两个部分。

xml定义如下:

<person> 
    <name>John Doe</name> 
    <email>jdoe@example.com</email> 
</person>

使用C++,访问方式可能如下:

cout << person.getTag("name").getText() << endl;

cout << person.getTag("email").getText() << endl;

如果用protobuf,则定义如下:

message person {
	optional string name = 1;
	optional string email = 2 ;
}

如果使用C++,访问方式可能如下:

cout << person.name() << endl;

cout << person.email() << endl;

访问方式更简单。

5.2、序列化后生成的代码体积更小

还是上述消息,使用protobuf进行编码后,体积大约是28个字节;而如果使用xml进行编码,则大约需要69个字节(除去空格)。

5.3、解析速度更快

对上述编码后的数据进行解析,protobuf需要100-200纳秒;而xml则需要5000-10000纳秒。

5.4、xml相比,protobuf的缺点是不易读

众所周知,xml是一种自描述语言,一看就可知道其作用,见文知意。而protobuf序列化后是一串二进制代码,如果没有对应的协议格式(即**.proto文件),想要读懂它难如登天。另外,如果目标是一种基于文版的标签式文档(如html**),则xml更具优势。

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

森明帮大于黑虎帮

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值