本文是对官方文档的翻译,然后截取了一篇非常优秀的文章片段来帮助理解,本人英文水平有限,基本都是直译,如果有不理解的地方请参考英文官方文档,参考的文章链接在文章末尾

protocol buffers简介

protocol buffer是google的一个开源项目,它是用于结构化数据串行化的灵活、高效、自动的方法,例如XML,不过它比xml更小、更快、也更简单。你可以定义自己的数据结构,然后使用代码生成器生成的代码来读写这个数据结构。你甚至可以在无需重新部署程序的情况下更新数据结构

protocol buffers是如何工作的

.proto文件定义消息,message是.proto文件最小的逻辑单元,由一系列name-value键值对构成。下面的.proto文件定义了一个"人"的消息:

message Person {
required string name = 1;
required int32 id = 2;
optional string email = 3; enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
} message PhoneNumber {
required string number = 1;
optional PhoneType type = 2 [default = HOME];
} repeated PhoneNumber phone = 4;
}

message消息包含一个或多个编号唯一的字段,每个字段由字段限制,字段类型,字段名和编号四部分组成,字段限制分为:optional(可选的)、required(必须的)以及repeated(重复的)。定义好消息后,使用ProtoBuf编译器生成C++对应的.h.cc文件,源文件提供了message消息的序列化和反序列化等方法:

# 序列化数据
Person person;
person.set_name("John Doe");
person.set_id(1234);
person.set_email("jdoe@example.com");
fstream output("myfile", ios::out | ios::binary);
person.SerializeToOstream(&output); # 反序列化数据
fstream input("myfile", ios::in | ios::binary);
Person person;
person.ParseFromIstream(&input);cout << "Name: " << person.name() << endl;cout << "E-mail: " << person.email() << endl;

为什么不直接使用XML

同XML相比,Protobuf的优势在于高性能,它以高效的二进制存储方式比XML小3到10倍,快20到100倍,原因在于:

  • ProtoBuf序列化后所生成的二进制消息非常紧凑
  • ProtoBuf封解包过程非常简单

Protobuf序列化

Varint简介

Varint 是一种紧凑的表示数字的方法。它用一个或多个字节来表示一个数字,值越小的数字使用越少的字节数。这能减少用来表示数字的字节数。

比如对于int32类型的数字,一般需要4个byte来表示,但是采用Varint对于很小的int32类型的数字,则可以用1个byte来表示。当然凡事都有好的也有不好的一面,采用Varint表示法,大的数字则需要5个byte来表示。从统计的角度来说,一般不会所有的消息中的数字都是大数,因此大多数情况下,采用Varint后可以用更少的字节数来表示数字信息。

Varint格式

Varint中的每个byte的最高位bit有特殊的含义,如果该位为1,表示后续的byte也是该数字的一部分,如果该位为0则结束,其他的7个bit都用来表示数字。因此小于128的数字都可以用一个byte表示,大于128的数字会用两个字节来表示。

Varint编解码

比如数值300用Varint来表示就是:1010 1100 0000 0010。下图演示了Google Protocol Buffer解析Varint表示的300的过程,由于Google Protocol Buffer采用小端字节序,所以实际存储的字节顺序是反过来的:

Google Protocol Buffer序列化

消息经过序列化后会成为一个二进制数据流,该流中的数据为一系列的Key-Value对。如下图所示:

采用这种Key-Pair结构无需使用分隔符来分割不同的 Field。对于可选的Field,如果消息中不存在该Field,那么在最终的Message Buffer中就没有该Field,这些特性都有助于节约消息本身的大小。Key 用来标识具体的Field,在解包的时候ProtoBuf根据Key就可以知道相应的Value应该对应于消息中的哪一个Field。Key由字段的编号和字段的线性传输类型构成(field_number << 3) | wire_type

wire_type Meaning Used For
0 Varint int32, int64, uint32, uint64, sint32, sint64, bool, enum
1 64-bit fixed64, sfixed64, double
2 Length-delimi string, bytes, embedded messages, packed repeated fields
3 Start group Groups (deprecated)
4 End group Groups (deprecated)
5 32-bit fixed32, sfixed32, float
  • Google Protocol Buffer采用zigzag编码来用无符号数来表示有符号数字,zigzag采用正数和负数交错的方式来同时表示无符号数来表示有符号数字,如图所示:

使用zigzag编码,绝对值小的数字,无论正负都可以采用较少的byte来表示,充分利用了Varint这种技术。

  • 其他的数据类型,比如字符串等则采用类似数据库中的varchar的表示方法,即用一个varint表示长度,然后将其余部分紧跟在这个长度部分之后即可。

ProtoBuf编码与XML编码对比

消息定义如下:

package lm;
message helloworld
{
required int32 id = 1; // ID
required string str = 2; // str
optional int32 opt = 3; //optional field
}

假设有一条helloworld消息id=101 str="hello",那么用Protobuf序列化后的字节序列为:

08 65 12 06 48 65 6C 6C 6F 77

而如果用XML,则类似这样:

31 30 31 3C 2F 69 64 3E 3C 6E 61 6D 65 3E 68 65
6C 6C 6F 3C 2F 6E 61 6D 65 3E 3C 2F 68 65 6C 6C
6F 77 6F 72 6C 64 3E 一共 55 个字节,这些奇怪的数字需要稍微解释一下,其含义用 ASCII 表示如下:
<helloworld>
<id>101</id>
<name>hello</name>
</helloworld></pre>

ProtoBuf封解包

首先我们来了解一下XML的封解包过程。XML需要从文件中读取出字符串,再转换为XML文档对象结构模型。之后再从XML文档对象结构模型中读取指定节点的字符串,最后再将这个字符串转换成指定类型的变量,这个过程非常复杂。其中将XML文件转换为文档对象结构模型的过程通常需要完成词法文法分析等大量消耗 CPU 的复杂计算。

反观Protobuf,它只需要简单地将一个二进制序列按照指定的格式读取到C++对应的结构类型中就可以了。从上一节的描述可以看到,消息的解码过程也可以通过几个位移操作组成的表达式计算即可完成,速度非常快。

上面例子中,Protobuf解包helloworld消息的过程可以用下图表示:

整个解析过程需要Protobuf本身的框架代码和由Protobuf编译器生成的代码共同完成。其中Message以及Message_lite作为通用的流程框架,CodedInputStreamWireFormatLite提供了对二进制数据的解码功能,而且Protobuf的解码可以通过几个简单的数学运算完成,无需复杂的词法语法分析,因此图中ReadTag()等方法都非常快。相对于XML的解析,整个调用路径上的其他类和方法都非常简单,这也就是ProtoBuf封解包速度迅速的原因。

参考资料

Google Protocol Buffer 的使用和原理

Protocol Buffers官方文档(开发指南)的更多相关文章

  1. Protocol Buffers官方文档(proto3语言指南)

    本文是对官方文档的翻译,大部分内容都是引用其他一些作者的优质翻译使文章内容更加通俗易懂(自己是直译,读起来有点绕口难理解,本人英文水平有限),参考的文章链接在文章末尾 这篇指南描述如何使用protoc ...

  2. 一起学微软Power BI系列-官方文档-入门指南(1)Power BI初步介绍

    我们在前一篇文章微软新神器-Power BI,一个简单易用,还用得起的BI产品中,我们初步介绍了Power BI的基本知识.由于Power BI是去年开始微软新发布的一个产品,虽然已经可以企业级应用, ...

  3. 一起学微软Power BI系列-官方文档-入门指南(2)获取源数据

    我们在文章: 一起学微软Power BI系列-官方文档-入门指南(1)Power BI初步介绍中,我们介绍了官方入门文档的第一章.今天继续给大家介绍官方文档中,如何获取数据源的相关内容.虽然是英文,但 ...

  4. 一起学微软Power BI系列-官方文档-入门指南(3)Power BI建模

    我们前2篇文章:一起学微软Power BI系列-官方文档-入门指南(1)Power BI初步介绍 和一起学微软Power BI系列-官方文档-入门指南(2)获取源数据 中,我们介绍了官方入门文档与获取 ...

  5. 一起学微软Power BI系列-官方文档-入门指南(4)Power BI的可视化

    在前面的系列文章中,我们介绍了官方有关获取数据,以及建模的原始文档和基本介绍.今天继续给大家介绍官方文档中,有关可视化的内容.实际上获获取数据和建模更注重业务关系的处理,而可视化则关注对数据的解读.这 ...

  6. 一起学微软Power BI系列-官方文档-入门指南(5)探索数据奥秘

    我们几篇系列文章中,我们介绍了官方入门文档与获取数据等基本知识.今天继续给大家另外一个重点,探索数据奥秘.有了数据源,有了模型,下一步就是如何解析数据了.解析数据的过程需要很多综合技能,不仅仅是需要掌 ...

  7. 一起学微软Power BI系列-官方文档-入门指南(6)Power BI与Excel

    今天介绍了官方入门文档中有关PowerBI和Excel的知识.前几篇入门文档有点仓促,加上最近时间的研究,会有更多技巧性和入门型的文章或者视频发布,最后2篇入门文档将更加详细一点,因为部分文章进行简单 ...

  8. 一起学微软Power BI系列-官方文档-入门指南(7)发布与共享-终结篇+完整PDF文档

    接触Power BI的时间也只有几个月,虽然花的时间不多,但通过各种渠道了解收集,谈不上精通,但对一些重要概念和细节还是有所了解.在整理官方文档的过程中,也熟悉和了解了很多概念.所以从前到后把微软官方 ...

  9. 【译】Spark官方文档——编程指南

    本文翻自官方博客,略有添加:https://github.com/mesos/spark/wiki/Spark-Programming-Guide Spark发指南 从高的面看,其实每一个Spark的 ...

随机推荐

  1. SpringMVC 文件上传及下载

    首先需要导入jar包 创建一个jsp页面 package cn.happy.Controller; import java.io.File; import javax.servlet.http.Htt ...

  2. JavaUtil_09_通用工具类-01_Hutool

    一.重要的官方资料 1. Hutool 官网 2. Hutool 参考文档 3. Hutool API文档

  3. listen and translation exercise 49

    Huh? Appears to Be Universally Understood What's the most universal utterance in languages across th ...

  4. 转载 解决Android与服务器交互大容量数据问题

    对于目前的状况来说,移动终端的网络状况没有PC网络状况那么理想.在一个Android应用中,如果需要接收来自服务器的大容量数据,那么就不得不考虑客户的流量问题.本文根据笔者的一个项目实战经验出发,解决 ...

  5. HTML5视音频标签参考

    本文将介绍HTML5中的视音频标签和对应的DOM对象.是相关资料的中文化版本,可以作为编写相关应用的简易中文参考手册. 一些约定 所有浏览器:指支持HTML5的常见桌面浏览器,包括IE9+.Firef ...

  6. 在Windows下编译WebRTC

    前言 这篇文章的目的在于为你节省生命中宝贵的10小时(甚至更多),或者浪费你10分钟.作为Google更新频繁的大型跨平台基础库,WebRTC的编译一直被人称为噩梦.如果恰巧你偏要在Windows下编 ...

  7. ACM学习历程—FZU2191完美的数字(数学)

    Description Bob是个很喜欢数字的孩子,现在他正在研究一个与数字相关的题目,我们知道一个数字的完美度是 把这个数字分解成三个整数相乘A*A*B(0<A<=B)的方法数,例如数字 ...

  8. python为类定义构造函数

    用python进行OO编程时, 经常会用到类的构造函数来初始化一些变量. class FileData:     def __init__(self, data, name, type):       ...

  9. SpringBoot系列(1)

    简介:用来简化新Spring应用的初始搭建以及开发过程:该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置. 特点:1. 创建独立的Spring应用程序2. 嵌入的Tomcat, ...

  10. React 特别需要注意的地方

    如图: