当前位置: 首页 > news >正文

protobuf中SerializeToString和SerializePartialToString的区别

文章目录

  • 前言
  • proto2
    • message定义
    • message扩展
    • 注意事项
  • proto3
  • 序列化
    • SerializeToString和SerializeAsString区别
    • SerializeToString和SerializePartialToString区别
  • 总结

前言

protobuf是Google提出的序列化方案,此方案独立于语言和平台,目前提供了如c++、go、python等多种语言的实现,使用比较广泛,具有性能开销小,压缩率高等优点,是值得学习的优秀开源库。

protobuf有 v2 和 v3 两个主要的并且差异很大的版本,有一些关于protobuf的文章中并没有说明版本,有些描述的内容给人造成了疑惑,所以在使用protobuf前要明确自己使用的版本,查找对应的特性。

proto2

这个版本在编写 .proto 文件时的字段有三种限定符,分别是requiredoptionalrepeated

  • required:必须设置该字段,如果是在debug模式下编译 libprotobuf,则序列化一个未初始化(未对required字段赋值)的 message 将导致断言失败。在release模式的构建中,将跳过检查并始终写入消息,但解析未初始化的消息将返回false表示失败。
  • optional:可以设置也可以不设置该字段。如果未设置可选字段值,则使用默认值,也可以用[default = value]进行设置。
  • repeated:该字段可以重复任意次数(包括零次),可以将 repeated 字段视为动态大小的数组。

message定义

定义一个简单的 message 结构如下:

message Person {
  required string name = 1;
  optional string email = 2;
  optional int age = 3 [default = 18];
  repeated bytes phones = 4;
}

观察 message 定义可以看到每个字段后面都有 = 1= 2 的标记,这些被称为 Tags,在 protobuf 中同一个 message 中的每个字段都需要有独一无二的tag,tag 为 1-15 的是单字节编码,16-2047 使用2字节编码,所以1-15应该给频繁使用的字段。

关于tag的取值,还有一种范围是[1,536870911]的说法,同时 19000 到 19999 之间的数字也不能使用,因为它们是 protobuf 的实现中保留的,也就是 FieldDescriptor::kFirstReservedNumberFieldDescriptor::kLastReservedNumber 指定的范围,如果使用其中的数字,导出 .proto 文件时会报错,此处存疑,需要验证一下。

message扩展

在使用的了 protobuf 的项目发布以后,绝对会遇到扩展原有 message 结构的需求,这一点不可避免,除非发布后的项目不再升级维护了,要想扩展就需要兼容之前的代码逻辑,这里有一些必须遵守的规则,否则就达不到兼容的目的。

  • 不能更改任何现有字段的 tag
  • 不能添加或删除任何 required 字段
  • 可以删除 optional 或 repeated 的字段
  • 可以添加新的 optional 或 repeated 字段,但必须使用新的tag,曾经使用过又删除的 tag 也不能再使用了

注意事项

proto2 中对 required 的使用永远都应该非常小心。如果想在某个时刻停止写入或发送 required 字段,直接将字段更改为可选字段将会有问题。一些工程师得出的经验是,使用 required 弊大于利,他们更喜欢只使用 optional 和 repeated。

proto3

proto3 比 proto2 支持更多语言但更简洁,去掉了一些复杂的语法和特性。

  • 在第一行非空白非注释行,必须写:syntax = "proto3";
  • 直接从语法层面上移除了 required 规则,取消了 required 限定词
  • 增加了对 Go、Ruby、JavaNano 等语言的支持
  • 移除了 default 选项,字段的默认值只能根据字段类型由系统决定

序列化

将 message 结构对象序列化的函数有很多,即使是序列化成字符串也有多个函数可以使用,比如 SerializeToStringSerializePartialToStringSerializeAsStringSerializePartialAsString 等等。

SerializeToString和SerializeAsString区别

这两个还是很好区分的,从源码角度一眼就能够分辨出来:

std::string MessageLite::SerializeAsString() const {
  // If the compiler implements the (Named) Return Value Optimization,
  // the local variable 'output' will not actually reside on the stack
  // of this function, but will be overlaid with the object that the
  // caller supplied for the return value to be constructed in.
  std::string output;
  if (!AppendToString(&output)) output.clear();
  return output;
}

bool MessageLite::SerializeToString(std::string* output) const {
  output->clear();
  return AppendToString(output);
}

从源代码可以很容易看出,两者仅仅是参数和返回值的类型不同,其内部调用的函数都是一样的,SerializePartialToStringSerializePartialAsString 两个函数也是这种区别,可以根据外部逻辑所需来调用合适的函数。

bool MessageLite::SerializePartialToString(std::string* output) const {
  output->clear();
  return AppendPartialToString(output);
}

std::string MessageLite::SerializePartialAsString() const {
  std::string output;
  if (!AppendPartialToString(&output)) output.clear();
  return output;
}

SerializeToString和SerializePartialToString区别

这两个函数的区别在于内部调用的函数不同,一个调用 AppendToString,另一个调用 AppendPartialToString,两个被调用函数的源代码如下:

bool MessageLite::AppendToString(std::string* output) const {
  GOOGLE_DCHECK(IsInitialized()) << InitializationErrorMessage("serialize", *this);
  return AppendPartialToString(output);
}

bool MessageLite::AppendPartialToString(std::string* output) const {
  size_t old_size = output->size();
  size_t byte_size = ByteSizeLong();
  if (byte_size > INT_MAX) {
    GOOGLE_LOG(ERROR) << GetTypeName()
               << " exceeded maximum protobuf size of 2GB: " << byte_size;
    return false;
}

原来 AppendToString 函数调用了 AppendPartialToString, 只是在调用之前先执行了一句 GOOGLE_DCHECK(IsInitialized()) << InitializationErrorMessage("serialize", *this); 这句话什么意思呢?

其实就是一个调试状态下的检查,类似于 assert 这个断言函数吧,检查的内容是判断这个 message 是否初始化,之前提到 required 修饰的字段必须要设置一个值,否者就是未初始化的状态,那么现在两个函数的区别就知道了,带有 “Partial” 函数其实是忽略 required 字段检查的,另外还有没有别的不同需要再进一步研究下源码了。

总结

  • protobuf有 v2 和 v3 两个主要的并且差异较大的版本,使用前请注意版本号
  • proto3 直接从语法层面上移除了 required 规则,移除了 default 选项,字段的默认值只能根据字段类型由系统决定
  • SerializeToString和SerializeAsString区别在于参数和返回值的不同,内部调用的函数是相同的
  • SerializeToString和SerializePartialToString区别在于SerializePartialToString会忽略 required 字段必须赋值的要求
  • 在应用过程中尽可能重用 message 结构,这样protobuf内部实现中内存的重用

==>> 反爬链接,请勿点击,原地爆炸,概不负责!<<==

没有什么捷径,继续努力就好了
短期内不会看到什么成果,甚至说一辈子都可能看不到,但只有努力了才有可能看得到

相关文章:

  • linux文件权限简单备忘知识点
  • 使用AddressSanitizer搭配addr2line查找C/C++内存泄漏问题
  • C++对我来说简直就是星辰大海,为了避免翻船,我选择从小河沟出发
  • cpplint中filter参数的每个可选项的含义
  • 手把手搭建一个redis集群
  • 换个角度来看看C++中的左值、右值、左值引用、右值引用
  • C/C++中的数据类型转换()/static_cast/dynamic_cast/const_cast/reinterpret_cast
  • C++11中std::move和std::forward到底干了啥
  • 使用box2dweb做一个下落的小球,宝宝玩的不亦乐乎
  • C++中使用std::sort自定义排序规则时要注意的崩溃问题
  • 从一个小题中的应用来体会下std::tie的便利之处
  • Floyd-Warshall——仅用4行代码就能解决多源最短路径问题的算法
  • Dijkstra——通过不断松弛来解决单源最短路径问题的算法
  • C++11中的std::atomic保证的原子性是什么
  • .bat批处理(十):从路径字符串中截取盘符、文件名、后缀名等信息
  • 9月CHINA-PUB-OPENDAY技术沙龙——IPHONE
  • __proto__ 和 prototype的关系
  • 【node学习】协程
  • Apache的80端口被占用以及访问时报错403
  • css布局,左右固定中间自适应实现
  • CSS相对定位
  • Golang-长连接-状态推送
  • javascript面向对象之创建对象
  • java多线程
  • mac修复ab及siege安装
  • miaov-React 最佳入门
  • Mithril.js 入门介绍
  • mongodb--安装和初步使用教程
  • Mysql优化
  • Python利用正则抓取网页内容保存到本地
  • SAP云平台运行环境Cloud Foundry和Neo的区别
  • springboot_database项目介绍
  • SpringBoot几种定时任务的实现方式
  • Swoft 源码剖析 - 代码自动更新机制
  • Webpack4 学习笔记 - 01:webpack的安装和简单配置
  • 案例分享〡三拾众筹持续交付开发流程支撑创新业务
  • 回顾 Swift 多平台移植进度 #2
  • 基于web的全景—— Pannellum小试
  • 看域名解析域名安全对SEO的影响
  • 力扣(LeetCode)965
  • 运行时添加log4j2的appender
  • 《天龙八部3D》Unity技术方案揭秘
  • 整理一些计算机基础知识!
  • ​2020 年大前端技术趋势解读
  • # 计算机视觉入门
  • #数学建模# 线性规划问题的Matlab求解
  • (16)UiBot:智能化软件机器人(以头歌抓取课程数据为例)
  • (LeetCode 49)Anagrams
  • (ZT)北大教授朱青生给学生的一封信:大学,更是一个科学的保证
  • (更新)A股上市公司华证ESG评级得分稳健性校验ESG得分年均值中位数(2009-2023年.12)
  • (六) ES6 新特性 —— 迭代器(iterator)
  • (十八)devops持续集成开发——使用docker安装部署jenkins流水线服务
  • (一)Dubbo快速入门、介绍、使用
  • (一)VirtualBox安装增强功能
  • (转)Android学习笔记 --- android任务栈和启动模式