技术标签: 计算机网络 Protocol Buffer
Json、XML
数据存储格式的你们,相信大多都没听过Protocol Buffer
Protocol Buffer
其实 是 Google
出品的一种轻量 & 高效的结构化数据存储格式,性能比 Json、XML
真的强!太!多!
由于
Protocol Buffer
已经具备足够的吸引力
今天,我将讲解为什么Protocol Buffer
的性能如此的好:
a. 序列化速度 & 反序列化速度快
b. 数据压缩效果好,即序列化后的数据量体积小
阅读本文前请先阅读:
1. 快来看看Google出品的Protocol Buffer,别只会用Json和XML了
2. 手把手教你如何安装Protocol Buffer
3. 这是一份很有诚意的 Protocol Buffer 语法详解
一种 结构化数据 的数据存储格式(类似于 XML、Json
)
Protocol Buffer
目前有两个版本:proto2
和proto3
- 因为
proto3
还是beta 版,所以本次讲解是proto2
通过将 结构化的数据 进行 串行化(序列化),从而实现 数据存储 / RPC 数据交换的功能
- 序列化: 将 数据结构或对象 转换成 二进制串 的过程
- 反序列化:将在序列化过程中所生成的二进制串 转换成 数据结构或者对象 的过程
XML、Json
数据存储格式,Protocol Buffer
有如下特点:
传输数据量大 & 网络环境不稳定 的数据存储、RPC 数据交换 的需求场景
如 即时IM (QQ、微信)的需求场景
在 传输数据量较大的需求场景下,Protocol Buffer
比XML、Json
更小、更快、使用 & 维护更简单!
关于 Protocol Buffer
的使用流程,具体请看我写的文章:快来看看Google出品的Protocol Buffer,别只会用Json和XML了
TCP/IP
模型(四层) & OSI
模型 (七层)
序列化 / 反序列化 属于 TCP/IP
模型 应用层 和 OSI`模型 展示层的主要功能:
所以, Protocol Buffer
属于 TCP/IP
模型的应用层 & OSI
模型的展示层
不同的计算机语言中,数据结构,对象以及二进制串的表示方式并不相同。
对于面向对象的语言(如Java
):对象 = Object
= 类的实例化;在Java
中最接近数据结构 即 POJO
(Plain Old Java Object
),或Javabean
(只有 setter/getter
方法的类)
对于半面向对象的语言(如C++
),对象 = class
,数据结构 = struct
C++
,因为具有内存操作符,所以 二进制串 容易理解:C++
的字符串可以直接被传输层使用,因为其本质上就是以 '\0'
结尾的存储在内存中的二进制串Java
,二进制串 = 字节数组 =byte[]
byte
属于Java
的八种基本数据类型
- 二进制串 容易和
String
混淆:String
一种特殊对象(Object)。对于跨语言间的通讯,序列化后的数据当然不能是某种语言的特殊数据类型。
T - L - V
的数据存储方式 定义
即 Tag - Length - Value
,标识 - 长度 - 字段值 存储方式
作用
以 标识 - 长度 - 字段值 表示单个数据,最终将所有数据拼接成一个 字节流,从而 实现 数据存储 的功能
其中
Length
可选存储,如 储存Varint
编码数据就不需要存储Length
示意图
T - L - V
存储方式的优点是 相应字段在解码时才会被设置为默认值
请记住Protocol Buffer
的 三个关于数据存储 的重要结论:
结论1: Protocol Buffer
将 消息里的每个字段 进行编码后,再利用T - L - V
存储方式 进行数据的存储,最终得到的是一个 二进制字节流
序列化 = 对数据进行编码 + 存储
结论2:Protocol Buffer
对于不同数据类型 采用不同的 序列化方式(编码方式 & 数据存储方式),如下图:
从上表可以看出:
1. 对于存储Varint
编码数据,就不需要存储字节长度 Length
,所以实际上Protocol Buffer
的存储方式是 T - V
;
2. 若Protocol Buffer
采用其他编码方式(如LENGTH_DELIMITED
)则采用T - L - V
Protocol Buffer
对于数据字段值的 独特编码方式 & T - L - V
数据存储方式,使得 Protocol Buffer
序列化后数据量体积如此小下面,我将对不同的编码方式 & 数据存储方式进行逐一讲解。
Varint
& Zigzag
Varint
编码方式介绍如:
- 对于int32
类型的数字,一般需要 4个字节 表示;
2. 若采用Varint
编码,对于很小的int32
类型 数字,则可以用 1个字节 来表示
3. 虽然大的数字会需要 5 个 字节 来表示,但大多数情况下,消息都不会有很大的数字,所以采用Varint
方法总是可以用更少的字节数来表示数字
从上面可看出:Varint
中每个 字节 的最高位 都有特殊含义:
所以,当使用
Varint
解码时时,只要读取到最高位为0的字节时,就表示已经是Varint
的最后一个字节
下面,我将用两个个例子来说明Varint
编码方式的使用
Varint
编码
从上面可以看出:
但采用 Varint
方法,对于很小的 Int32
类型 数字(小于256),则可以用 1个字节 来表示;
以此类推,比如300也只需要2个字节
虽然大的数字会需要 5 个字节 来表示,但大多数情况下,消息都不会有很大的数字
Varint
方法总是可以用更少的字节数来表示数字,从而更好地实现数据压缩 下面继续看如何解析经过Varint
编码的字节
Varint
编码方式的不足因为计算机定义负数的符号位为数字的最高位
Varint
编码方式 表示一个负数,那么一定需要 5 个 byte(因为负数的最高位是1,会被当做很大的整数去处理)Protocol Buffer
定义了 sint32 / sint64
类型表示负数,通过先采用 Zigzag
编码(将 有符号数 转换成 无符号数),再采用 Varint
编码,从而用于减少编码后的字节数 int32 / int64
类型的字段值(正数),
Protocol Buffer
直接采用
Varint
编码
sint32 / sint64
类型的字段值(负数),
Protocol Buffer
会先采用
Zigzag
编码,再采用
Varint
编码
Protocol Buffer
在 Varint
编码上又增加了Zigzag
编码方式进行编码Zigzag
编码方式Zigzag
编码方式详解特别是对 表示负数的数据 能更好地进行数据压缩
-2
进行 Zigzag
编码:
Zigzag
编码 是补充 Varint
编码在 表示负数 的不足,从而更好的帮助 Protocol Buffer
进行数据的压缩sint32 / sint64
数据类型 Protocol Buffer
通过Varint
和Zigzag
编码后大大减少了字段值占用字节数。
T - V
Protocol Buffer
采用 Varint
& Zigzag
编码后,以 T - V
方式进行数据存储 对于
Varint
&Zigzag
编码,省略了T - L - V
中的字节长度Length
Protocol Buffer
采用Varint
& Zigzag
编码后 的消息字段 标识号 & 数据类型 的值作用:标识 字段
- 存储了字段的标识号(
field_number
)和 数据类型(wire_type
),即Tag
= 字段数据类型(wire_type
) + 标识号(field_number
)- 占用 一个字节 的长度(如果标识号超过了16,则占用多一个字节的位置)
- 解包时,
Protocol Buffer
根据Tag
将Value
对应于消息中的 字段
具体使用
经过 Protocol Buffer
采用Varint
& Zigzag
编码后 的消息字段的值
下面通过一个实例进行整个编码过程的说明:
64(32)-bit
编码方式较简单:编码后的数据具备固定大小 = 64位(8字节) / 32位(4字节) 两种情况下,都是高位在后,低位在前
T - V
方式进行数据存储,同上。
T - L - V
此处主要讲解三种数据类型:
String
类型Message
)packed
修饰的 repeat
字段(即packed repeated fields
) 字段值(即V
) 采用UTF-8
编码
T - L - V
- 外部消息的
V
即为 嵌套消息的字段
- 在
T - L -V
里嵌套了一系列的T - L -V
- 编码方式:字段值(即
V
) 根据字段的数据类型采用不同编码方式
编码 & 存储方式如下
packed
修饰的 repeat
字段 repeated
修饰的字段有两种表达方式:
背景:对于同一个 repeated
字段、多个字段值来说,他们的Tag都是相同的,即数据类型 & 标识号都相同
repeated
类型可以看成是数组
问题:若以传统的多个 T - V对存储(不带packed=true
),则会导致Tag的冗余,即相同的Tag存储多次;
packed=true
的 repeated
字段存储方式,即将相同的 Tag
只存储一次、添加 repeated
字段下所有字段值的长度Length
、连续存储 repeated
字段值,组成一个大的Tag - Length - Value -Value -Value
对,即T - L - V - V - V
对。
通过采用带packed=true
的 repeated
字段存储方式,从而更好地压缩序列化后的数据长度。
Protocol Buffer
的 packed
修饰只用于repeated
字段 或 基本类型的repeated
字段.proto
文件时会报错required
字段没有被设置字段值,那么在IsInitialized()
进行初始化检查会报错并提示失败 所以
required
字段必须要被设置字段值
注意2:序列化顺序 是根据 Tag
标识号 从小到大 进行编码
和
.proto
文件内 字段定义的数据无关
注意3:T - V
的数据存储方式保证了Protobuf
的版本兼容:高<->低 或 低<->高都可以适配
若新版本 增加了
required
字段, 旧版本 在数据解码时会认为IsInitialized()
失败,所以慎用required
字段
根据上面的序列化原理分析,我总结出以下Protocol Buffer
的使用建议
通过下面建议能有效降低序列化后数据量的大小
建议1:多用 optional
或 repeated
修饰符
因为若optional
或 repeated
字段没有被设置字段值,那么该字段在序列化时的数据中是完全不存在的,即不需要进行编码
相应的字段在解码时才会被设置为默认值
建议2:字段标识号(Field_Number
)尽量只使用 1-15,且不要跳动使用
因为Tag
里的Field_Number
是需要占字节空间的。如果Field_Number
>16时,Field_Number
的编码就会占用2个字节,那么Tag
在编码时也就会占用更多的字节;如果将字段标识号定义为连续递增的数值,将获得更好的编码和解码性能
建议3:若需要使用的字段值出现负数,请使用 sint32 / sint64
,不要使用int32 / int64
因为采用sint32 / sint64
数据类型表示负数时,会先采用Zigzag
编码再采用Varint
编码,从而更加有效压缩数据
建议4:对于repeated
字段,尽量增加packed=true
修饰
因为加了packed=true
修饰repeated
字段采用连续数据存储方式,即T - L - V - V -V
方式
Protocol Buffer
除了序列化 & 反序列化后的数据体积小,序列化 & 反序列化的速度也非常快Protocol Buffer
的序列化 & 反序列化过程 序列化过程如下:
1. 判断每个字段是否有设置值,有值才进行编码
2. 根据 字段标识号&数据类型 将 字段值 通过不同的编码方式进行编码
由于:
a. 编码方式简单(只需要简单的数学运算 = 位移等等)
b. 采用 Protocol Buffer
自身的框架代码 和 编译器 共同完成
所以Protocol Buffer
的序列化速度非常快。
反序列化过程如下:
1. 调用 消息类的 parseFrom(input)
解析从输入流读入的二进制字节数据流
从上面可知,
Protocol Buffer
解析过程只需要通过简单的解码方式即可完成,无需复杂的词法语法分析,因此 解析速度非常快。
2. 将解析出来的数据 按照指定的格式读取到Java
、C++
、Phyton
对应的结构类型中
由于:
a. 解码方式简单(只需要简单的数学运算 = 位移等等)
b. 采用 Protocol Buffer
自身的框架代码 和 编译器 共同完成
所以Protocol Buffer
的反序列化速度非常快。
XML
的序列化 & 反序列化过程 XML的反序列化过程如下:
1. 从文件中读取出字符串
2. 将字符串转换为 XML
文档对象结构模型
3. 从 XML
文档对象结构模型中读取指定节点的字符串
4. 将该字符串转换成指定类型的变量
上述过程非常复杂,其中,将 XML
文件转换为文档对象结构模型的过程通常需要完成词法文法分析等大量消耗 CPU 的复杂计算。
因为序列化 & 反序列化过程简单,所以序列化 & 反序列化过程速度非常快,这也是 Protocol Buffer
效率高的原因
Protocol Buffer
的序列化 & 反序列化简单 & 速度快的原因是:
a. 编码 / 解码 方式简单(只需要简单的数学运算 = 位移等等)
b. 采用 Protocol Buffer
自身的框架代码 和 编译器 共同完成
Protocol Buffer
的数据压缩效果好(即序列化后的数据量体积小)的原因是:
a. 采用了独特的编码方式,如Varint
、Zigzag
编码方式等等
b. 采用T - L - V
的数据存储方式:减少了分隔符的使用 & 数据存储得紧凑
Protocol Buffer
系列文章,请看: 接下来我会讲解Protocol Buffer
的源码分析,有兴趣可以继续关注Carson_Ho的安卓开发笔记
文章浏览阅读1k次。 深入了解C语言(函数的参数传递和函数使用参数的方法) 深入了解C语言(函数的参数传递和函数使用参数的方法)tangl_99(原作) C语言生成的代码在执行效率上比其它高级语言都高.现在让我们来看看C语言生成的代码具体是什么样子的.当你看完本文对于C语言的了解一定会更深一步了. 本文通过一个个实际案例程序来讲解C语言. 研究案例一 工具: Turboc C v2.0,Debug_c语言中prog03_06了解函数
文章浏览阅读1.1k次,点赞18次,收藏14次。本节介绍了按钮控件的常见用法,包括:如何设置大小写属性与点击属性,如何响应按钮的点击事件和长按事件,如何禁用按钮又该如何启用按钮,等等。_通过按钮的点击事件控制图片的现实和隐藏
文章浏览阅读73次。day02 数据类型1.回顾什么叫js?基于对象和事件驱动的解释性脚本语言js的组成ECMAScript:JavaScript的标准DOM:Document Object Model 文档对象模型BOM:Browser Object Model 浏览器对象模型JavaScript和ECMAScript的关系?前者是后者的具体实现后者是前者的标准引入方式变量存储数据的容器声明变量:var 变量名var a; //undefined命名规则:1_"ypeof callback == \"function\"如何写"
文章浏览阅读2.1k次。FX3 JLINK调试是一个有些麻烦的事情,经常有些莫名其妙的问题。 设置参见 c:\Program Files (x86)\Cypress\EZ-USB FX3 SDK\1.3\doc\firmware 下的 EzUsbSuite_UG.pdf 文档。 常见问题: 1.装了多个版本的jlink,使用了未注册或不适当的版本 选择一个正确的版本。JLinkARM_V408l,JLinkA_ezusbsuite_qsg.pdf
文章浏览阅读2.6k次。** 本文仅通过用openGL+QT简单实现二进制stl文件读取显示并通过鼠标旋转缩放, 是比较入门的级别,由于个人能力有限,新手级别,所以未能施加光影灯光等操作, 未能让显示的stl文件更加真实。****效果图:**1. main.cpp```cpp#include "widget.h"#include <QApplication>int main(int argc, char *argv[]){ QApplication a(argc, argv); _qopengl如何鼠标控制旋转
文章浏览阅读943次,点赞22次,收藏19次。以大规模预训练语言模型为基础的chatgpt成功出圈,在近几日已经给人工智能板块带来了多次涨停,这足够说明这一风口的到来。而作为曾经的风口“知识图谱”而言,如何找到其与chatgpt之间的区别,找好自身的定位显得尤为重要。形式化知识和参数化知识在表现形式上一直都是大家考虑的问题,两种技术都应该有自己的定位与价值所在。知识图谱构建往往是抽取式的,而且往往包含一系列知识冲突检测、消解过程,整个过程都能溯源。以这样的知识作为输入,能在相当程度上解决当前ChatGPT的事实谬误问题,并具有可解释性。
文章浏览阅读601次。本实验介绍了适用于 Oracle Spatial Studio。他既可以在云上,也可以在本地作为Java应用部署。介绍详见这里。此实验申请地址在这里,时间为120分钟。此实验的帮助见这里。本实验使用的地图为OpenStreetMap,即免费的维基世界地图。此实验会自动创建一个ADW,需要通过OCI Console完成初始化配置,然后可以通过网页访问Spatial Studio简介在本次研讨会中,您将探索 Spatial Studio 用于自助式空间分析和可视化的功能。 使用交通事故、警察局和警察_oracle_spatial 可视化
文章浏览阅读1.3k次。改变Block UI界面的尺寸_ug二次开发 调整 对话框大小
文章浏览阅读1.3w次,点赞18次,收藏291次。基于深度学习的股票预测数据获取数据转换LSTM模型搭建训练模型预测结果数据获取采用tushare的数据接口(不知道tushare的筒子们自行百度一下,简而言之其免费提供各类金融数据 , 助力智能投资与创新型投资。)python可以直接使用pip安装tushare!pip install tushareCollecting tushare Downloading https://files.pythonhosted.org/packages/17/76/dc6784a1c07ec040e74_基于深度学习的股票操纵识别研究python代码
文章浏览阅读2k次。【IT168 厂商动态】 近日,北京中科网威(NETPOWER)工业级防火墙通过了中国电力工业电力设备及仪表质量检验测试中心(厂站自动化及远动)测试,并成为中国首家通过电力协议访问控制专业测评的工业级防火墙生产厂商。 北京中科网威(NETPOWER)工业级防火墙专为工业及恶劣环境下的网络安全需求而设计,它采用了非X86的高可靠嵌入式处理器并采用无风扇设计,整机功耗不到22W,具备极_电力行业防火墙有哪些
文章浏览阅读206次。/*烟台大学计算机学院 作者:董玉祥 完成日期: 2017 12 3 问题描述:二叉树排序树中查找的路径 */#include #include #define MaxSize 100typedef int KeyType; //定义关键字类型typedef char InfoType;typedef struct node
文章浏览阅读775次。当时老师一定会告诉你,这个一个"warning"的报警,可以不用管它,也确实如此。不过,这条报警信息我们至少可以知道一点,就是scanf函数调用完之后是有一个返回值的,下面我们就要对scanf返回值进行详细的讨论。并给出在编程时利用scanf的返回值可以实现的一些功能。_c语言ignoring return value