Protobuf 编码结构

news2024/9/22 21:35:49
编码结构
什么是protobuf

protocol buffers 是一种语言无关、平台无关、可扩展的序列化结构数据的方法,可用于数据通信协议和数据存储等,它是 Google 提供的一个具有高效协议数据交换格式工具库,是一种灵活、高效和自动化机制的结构数据序列化方法。相比XML,有编码后体积更小,编解码速度更快的优势;相比于 Json,Protobuf 有更高的转化效率。

protobuf优点

1、性能好/效率高

  • 时间开销:XML 格式化(序列化)的开销还好;但是 XML 解析(反序列化)的开销就不敢恭维了。 但是 protobuf 在这个方面就进行了优化。可以使序列化和反序列化的时间开销都减短。
  • 空间开销:也减少了很多

2、支持多种编程语言

protobuf缺点

1、二进制格式导致可读性差

为了提高性能,protobuf 采用了二进制格式进行编码。这直接导致了可读性差,影响开发测试时候的效率。当然,在一般情况下,protobuf 非常可靠,并不会出现太大的问题。

2、缺乏自描述

一般来说,XML 是自描述的,而 protobuf 格式则不是。它是一段二进制格式的协议内容,并且不配合写好的结构体是看不出来什么作用的。

3、通用性差

protobuf 虽然支持了大量语言的序列化和反序列化,但仍然并不是一个跨平台和语言的传输标准。在多平台消息传递中,对其他项目的兼容性并不是很好,需要做相应的适配改造工作。相比 json 和 XML,通用性还是没那么好。

字段类型与语言类型映射
.proto TypeC++ Type
doubledouble
floatfloat
int32int32
int64int64
uint32uint32
uint64uint64
sint32int32
sint64int64
fixed32uint32
fixed64uint64
sfixed32int32
sfixed64int64
boolbool
stringstring
bytesstring
枚举类型

在定义消息的时候,希望字段的值只能是预期某些值中的一个。

例如,现在为 SearchRequest 添加 corpus 字段,它的值只能是 UNIVERSAL、WEB、IMAGES、LOCAL、NEWS、PRODUCTS 和 VIDEO 中的一个。可以非常简单的通过向消息定义中添加枚举,并为每个可能的枚举值添加常量来实现。

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
  enum Corpus {
    UNIVERSAL = 0;
    WEB = 1;
    IMAGES = 2;
    LOCAL = 3;
    NEWS = 4;
    PRODUCTS = 5;
    VIDEO = 6;
  }
  Corpus corpus = 4;
}

Corpus 枚举的第一个常量必须映射到 0,所有枚举定义都需要包含一个常量映射到 0,并且该值为枚举定义的第一行内容。

导入其他proto

在一个 .proto 文件中可以导入其他 .proto 文件,这样就可以使用它导入的 .proto 中定义的消息类型了。

复制代码

import "myproject/other_protos.proto";

默认情况下,只能使用直接导入的 .proto 文件中定义的消息。但是,有时候可能需要将 .proto 文件移动到新位置,有一种巧妙的做法是在旧位置放一个虚拟的 .proto 文件。在文件中使用 import public 语法将所有导入转发到新位置,而不是直接移动 .proto 文件并在一次更改中更新所有调用点。任何导入包含 import public 语句中的 proto 文件的地方都可以传递依赖导入的公共依赖项。下面同一个例子来理解这里的内容。

在当前的文件夹下有 a.protob.proto 文件,现在在 a.proto 文件中 import 了 b.proto 文件。即在 a.proto 文件中有下面的内容

复制代码

import "b.proto";

假设现在 b.proto 中的消息要放入到一个 common/com.proto 文件中,可以方便其他地方也使用,这时可以修改 b.proto 在里面 import com.proto 即可.注意要「import public」, 因为单独的 import 只能使用 b.proto 中定义的消息,并不能使用 b.proto 中 import 的 proto 文件中的消息类型。

复制代码

// b.proto文件, 将里面的消息定义移动了common/com.proto文件,
// 在里面添加下面的import语句

import public "common/com.proto"

在使用 protoc 编译时,需要使用选项 -I 或 --proto_path 通知 protoc 去什么地方查找 import 的文件,如果不指定搜索路径,protoc 将会在当前目录下(调用protoc的路径)下查找。

可以导入 proto2 版本中的消息类型到 proto3 文件中使用,也可以在 proto2 文件中导入 proto3 版本的消息类型。但是在 proto2 的枚举类型不能直接应用到proto3的语法中。

嵌套消息

消息类型可以定义在消息类型的内部,即嵌套定义,里面下面的 Result 类型定义在 SearchResponse 的内部。不单单是一层嵌套,也可以多层嵌套。

复制代码

message SearchResponse {
  message Result {
    string url = 1;
    string title = 2;
    repeated string snippets = 3;
  }
  repeated Result results = 1;
}

外面的消息类型使用其他消息内部的消息,下面的 SomeOtherMessage 类型使用到了 Result,可以使用 SearchResponse.Result。

复制代码

message SomeOtherMessage {
  SearchResponse.Result result = 1;
}
未知字段

未知字段是 proto 编译器无法识别的字段,例如当旧二进制文件解析具有新字段的新二进制文件发送的数据时,这些新字段将成为旧二进制文件中的未知的字段。在初版的 proto3 中消息解析时会丢掉未知的字段,但在 3.5 版本时,重新引入了未知字段的保留,未知字段在解析期间会保留,并包含在序列化输出中。

编码原理

protobuf高效的秘密在于它的编码格式,它采用了 TLV(tag-length-value) 编码格式。每个字段都有唯一的 tag 值,它是字段的唯一标识。length 表示 value 数据的长度,length 不是必须的,对于固定长度的 value,是没有 length 的。value 是数据本身的内容。
img

对于 tag 值,它有 field_number 和 wire_type 两部分组成。field_number 就是在前面的 message 中我们给每个字段的编号,wire_type 表示类型,是固定长度还是变长的。 wire_type 当前有0到5一共6个值,所以用3个 bit 就可以表示这6个值。tag 结构如下图。
img

wire_type 值如下表, 其中3和4已经废弃,我们只需要关心剩下的4种。对于 Varint 编码数据,不需要存储字节长度 length。这种情况下,TLV 编码格式退化成 TV 编码。对于64-bit和32-bit也不需要 length,因为type值已经表明了长度是8字节还是4字节。

img

Varint编码原理

Varint 顾名思义就可变的 int,是一种变长的编码方式。值越小的数字,使用越少的字节表示,通过减少表示数字的字节数从而进行数据压缩。对于 int32 类型的数字,一般需要4个字节表示,但是采用 Varint 编码,对于小于128的 int32 类型的数字,用1个字节来表示。对于很大的数字可能需要5个字节来表示,但是在大多数情况下,消息中一般不会有很大的数字,所以采用 Varint 编码可以用更少的字节数来表示数字。Varint 是变长编码,那它是怎么区分出各个字段的呢?也就是怎么识别出这个数字是1个字节还是2个字节,Varint 通过每个字节的最高位来识别,如果字节的最高位是1,表示后续的字节也是该数字的一部分,如果是0,表示这是最后一个字节,且剩余7位都用来表示数字。虽然这样每个字节会浪费掉 1bit 空间,也就是 1/8=12.5% 的浪费,但是如果有很多数字不用固定的4字节,还是能节省不少空间。

下面通过一个例子来详细学习编码方法,现在有一个int32类型的数字65,它的Varint编码过程如下,可以看到占用4字节的65编码后只占用1个字节。

img

int32类型的数字128编码过程如下,4字节的128编码后只占用2个字节。

img

对于 Varint 解码是上面过程的一个逆过程,也比较简单,这里就不在举例说明了。

Zigzag编码

我们知道,负数的符号位为数字的最高位,它的最高位是1,所以对于负数用 Varint 编码一定为占用5个字节。这是不划算的,明明是4字节可以搞定的,现在统统都需要5个字节。所以 protobuf 定义了 sint32 和 sint64 类型来表示负数,先采用 Zigzag 编码,将有符号的数转成无符号的数,在采用 Varint 编码,从而减少编码后字节数。

Zigzag采用无符号数来表示有符号数,使得绝对值小的数字可以采用比较少的字节来表示。在理解Zigzag编码之前,我们先来看几个概念。

原码:最高位为符号位,剩余位表示绝对值 反码:除符号位外,对原码剩余位依次取反 补码:对于正数,补码为其本身,对于负数,除符号位外对原码剩余位依次取反然后+1

下面以int32类型的数-2为例,分析它的编码过程。如下图所示。
img
总结起来,对于负数对其补码做运算操作,对于数n,如果是 sint32 类型,则执行(n<<1)(n>>31)操作,如果是sint64则执行(n<<1)(n>>63), 通过前面的操作将一个负数变成了正数。这个过程就是 Zigzag 编码,最后在采用 Varint 编码。

因为 Varint 和 Zigzag 编码可以自解析内容的长度,所以可以省略长度项。TLV 存储简化为了 TV 存储,不需 length 项。

img

前面讲解了每个字段有 tag 和 value 构成,对于 string 类型,还有 length 字段。下面来看 tag 和 value 值的计算方法。

tag

tag中存储了字段的标识信息和数据类型信息,也就是说 tag=wire_type (字段数据类型)+ field_number (标识号)。通过 tag 可以获取它的字段编号,对应上定义的消息字段。计算公式为tag=field_number<<3 | wire_type, 然后在对其采用 Varint 编码。

value

value是采用Varint和Zigzag编码后的消息字段的值。下面是各个 wire_type 对应的存储类型一个总结。
img

wire_type编码方法编码长度存储方式数据类型
0Varint变长T-Vint32 int64 uint32 uint64 bool enum
0Zigzag+Varint变长T-Vsint32 sint64
164-bit固定8字节T-Vfixed64 sfixed64 double
2length-delimi变长T-L-Vstring bytes packed repeated fields embedded
3start group已废弃已废弃
4end group已废弃已废弃
532-bit固定4字节T-Vfixed32 sfixed32 float
string编码

字段类型为 string 类型,字段值采用 UTF-8 编码,下面是一个字符串编码的示例,字段序列号为1,编码的字符串内容是“China中国人”, proto 编码之后的内容见下面的输出。

复制代码

message stringEncodeTest {
  string test = 1;
}

func stringEncodeTest(){
 vs:=&api.StringEncodeTest{
  Test:"China中国人",
 }
 data,err:=proto.Marshal(vs)
 if err!=nil{
  fmt.Println(err)
  return
 }
 fmt.Printf("%v\n",data)
}

编码之后的二进制内容如下,第一个字节内容tag值,第二个字节内容14是 length,表示后面的字符串有14个字节。为啥是14个字节呢?“China中国人”不是8个字节吗?因为字符串采用的是UTF-8编码,每个中文字用3个字节编码,所以"中国人"编码之后占9个字节,在加上前面的China,一共是14个字节。

复制代码

[10 14 67 104 105 110 97 228 184 173 229 155 189 228 186 186]

img

img

嵌套类型编码

嵌套消息就是value又是一个字段消息,外层消息存储采用 TLV 存储,它的 value 又是一个 TLV 存储。整个编码结构如下图所示。

img

带有 packed 的 repeated 字段

repeaded 修饰的字段可以带 packed 或者不带。对于同一个 repeated 字段,多个字段值来说,它们的 tag 都是相同的,即数据类型和字段序号都相同。如果采用多个 TV 存储,则存在 tag 的冗余。如果设置 packed=true 的 repeated 字段存储方式,即相同的 tag 只存储一次,添加 repeated 字段下所有值的长度 length,构成 TLVVV… 存储结构,可以压缩序列化后数据长度,节省传输开销。

复制代码

message repeatedEncodeTest{
   // 方式1,不带packed
   repeated int32 cat = 1;
   // 方式2,带packed
   repeated  int32 dog = 2 [packed=true];
}
的 repeated 字段存储方式,即相同的 tag 只存储一次,添加 repeated 字段下所有值的长度 length,构成 TLVVV... 存储结构,可以压缩序列化后数据长度,节省传输开销。

复制代码

```go
message repeatedEncodeTest{
   // 方式1,不带packed
   repeated int32 cat = 1;
   // 方式2,带packed
   repeated  int32 dog = 2 [packed=true];
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1360855.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

24款奔驰C260L升级C63包围 渣男的外表

今天店里来了一台24款奔驰C260L 一提车就过来升级 我们公司还有包上牌服务 车主说 升级完包围 帮忙安排一下 原车的包围 没有那么霸气 特别是后杠 光溜溜的 升级后 四出尾喉 尾翼 直接牌面就起来了&#xff0c;星骏汇小许Xjh15863

真核微生物基因序列鉴定工具EukRep工具的安装和详细使用方法

介绍 EukRep是一种用于鉴定并分析环境中的真核微生物的工具。它基于16S rRNA基因序列&#xff0c;可以帮助研究人员确定和分类环境样品中存在的真核微生物群落。 EukRep 从宏基因组数据集中分类真核和原核序列 安装 要求Python3 推荐使用conda安装&#xff1a; $ conda cre…

HUAWEI华为荣耀MagicBook X 15酷睿i5-10210U处理器集显(BBR-WAH9)笔记本电脑原装出厂Windows10系统

链接&#xff1a;https://pan.baidu.com/s/1YVcnOP5YKfFOoLt0z706rg?pwdfwp0 提取码&#xff1a;fwp0 MagicBook荣耀原厂Win10系统自带所有驱动、出厂主题壁纸、系统属性专属LOGO标志、Office办公软件、华为/荣耀电脑管家等预装程序 文件格式&#xff1a;esd/wim/swm 安装…

【项目实战】分布式计算和通信框架(AKKA)入门介绍

一、AKKA是什么&#xff1f; Akka是一个用于构建高并发、分布式、可容错、事件驱动的应用程序的工具包和运行时。它基于Actor模型&#xff0c;提供了一种高效的并发编程模型&#xff0c;可以轻松地编写出高并发、分布式、可容错的应用程序。Akka还提供了一些常用的组件&#xf…

DMX512输出协议详解

目录 ​编辑 1、DMX512协议简介 2、DMX512协议分析 DMX512指令帧介绍 DMX512信息包 3、DMX512接口电路 4、参考代码 1、DMX512协议简介 DMX512是一种用于舞台灯光控制的数字传输协议。它是由美国舞台灯光协会&#xff08;USITT&#xff09;于1990年发布的工业标准&…

利用小红书笔记详情API:为内容运营提供强大的支持

利用小红书笔记详情API&#xff0c;内容运营者可以获得对小红书平台上的笔记内容的深入洞察&#xff0c;从而为其运营工作提供强大的支持。以下是该API如何支持内容运营的几个关键方面&#xff1a; 获取笔记内容与数据&#xff1a; API允许内容运营者直接获取小红书平台上的笔记…

Python轴承故障诊断 (九)基于VMD+CNN-BiLSTM的故障分类

往期精彩内容&#xff1a; Python-凯斯西储大学&#xff08;CWRU&#xff09;轴承数据解读与分类处理 Python轴承故障诊断 (一)短时傅里叶变换STFT Python轴承故障诊断 (二)连续小波变换CWT_pyts 小波变换 故障-CSDN博客 Python轴承故障诊断 (三)经验模态分解EMD_轴承诊断 …

57.6K star!一个免费开源的 API 开发生态系统

&#xff01;&#xff01;&#xff01;文末有链接&#xff01;&#xff01;&#xff01; 小伙伴们&#xff0c;你们有没有遇到这样的问题呢&#xff1f;当你作为前端开发者和后端开发者一起协同工作时&#xff0c;联调接口成了必须要做的工作。 而为了验证接口的稳定性和安全…

Python武器库开发-武器库篇之子域名扫描器开发(四十一)

Python武器库开发-武器库篇之子域名扫描器开发(四十一) 在我们做红队攻防或者渗透测试的过程中&#xff0c;信息收集往往都是第一步的&#xff0c;有人说&#xff1a;渗透的本质就是信息收集&#xff0c;前期好的信息收集很大程度上决定了渗透的质量和攻击面&#xff0c;本文将…

LabVIEW在高级结构监测中的创新应用

LabVIEW在高级结构监测中的创新应用 LabVIEW作为一个强大的系统设计平台&#xff0c;其在基于BOTDA&#xff08;光时域反射分析&#xff09;技术的结构监测中发挥着核心作用。利用LabVIEW的高效数据处理能力和友好的用户界面&#xff0c;开发了一个先进的监测系统。该系统专门…

ThreadPoolExecutor中的keepAliveTime详解

一.keepAliveTime的概念&#xff1a; keepAliveTime的单位是纳秒&#xff0c;即1s1000000000ns&#xff0c;1秒等于10亿纳秒。 keepAliveTime是线程池中空闲线程等待工作的超时时间。 当线程池中线程数量大于corePoolSize&#xff08;核心线程数量&#xff09;或设置了allowCor…

反诈宣传进社区 安全防护零距离

反诈宣传进社区 安全防护零距离 广州党建引领城中村治理志愿服务项目之“我爱我家”助建幸福网格项目&#xff0c;由中共广州市委政法委员会、广州市精神文明建设委员会办公室、广州市民政局广州市文化电旅游局、广州市来穗人员服务管理局、广州市消防救援支队、共青团广州市委…

STM32CubeMX之GPIO中断

参考文章《【STM32】HAL库 STM32CubeMX教程三----外部中断(HAL库GPIO讲解》 1 STM32CubeMX参数配置及其说明 配置好之后&#xff0c;生成代码 2 工程中的代码 用户在此函数中实现中断代码即可 /*** brief EXTI line detection callbacks.* param GPIO_Pin: Specifies the …

【计算机毕业设计】SSM停车场管理系统

项目介绍 本项目为后台管理系统&#xff0c;分为管理员、用户、工作人员三种角色&#xff1b; 管理员角色包含以下功能&#xff1a; 登陆页面,收入查看,停车卡开卡等功能。 用户角色包含以下功能&#xff1a; 用户登录,停车卡管理,优惠券管理,发送站内信,查看停车记录等功能…

wordpress个人博客/杂志主题Pin Premium

Pin Premium WordPress主题是针对博主的时尚且自适应的Pinterest风格主题。使用HTML5和CSS3技术创建&#xff0c;带有有效代码(两个演示)&#xff0c;完全响应&#xff0c;在所有移动设备上看起来完美&#xff0c;可在任何设备和 PC 上轻松使用。 响应式设计针对平板电脑和智能…

基于 Python+Django 技术栈,我开发了一款视频管理系统

学习过程中&#xff0c;遇到问题可以咨询作者 大家好&#xff0c;作为一名开发人员&#xff0c;平时比较愿意动手尝试各种有意思工具&#xff0c;因为笔者非常喜欢观看视频&#xff0c;尤其是YouTube、bilibili都是笔者非常喜欢的视频网站&#xff0c;所以想自己实现一个视频点…

astadmin安装querylist插件Puppeteer

我本来是想在linux服务器上安装&#xff0c;折腾了一天也没安装成功&#xff0c;由于急着用&#xff0c;就先做window10上安装了&#xff0c;以后有时间再研究centos7上安装 一 首先需要安装fastadmin 框架和querylist插件 这个大家可以自行安装&#xff0c;querylist安装地址…

ChatGPT大升级,文档图像识别领域迎来技术革新

​写在前面ChatGPT迎来重大升级冲击与机遇并存​大模型时代的思考与探索■ 像素级OCR统一模型- UPOCR■ OCR大一统模型- SPTS v3■ 文档识别分析LLM应用 写在最后问卷抽奖 ​写在前面 2023 年 12 月 31 日第十九届中国图象图形学学会青年科学家会议在广州召开&#xff0c;该会…

Linux ssh 实现远程免密登录

一、背景 我搭建了一个 zookeeper 集群&#xff0c;写了一个 shell 脚本来控制集群的启动和关闭&#xff0c;但是我发现每次我执行 shell 脚本的时候&#xff0c;都需要我输入各个服务器的密码才可以运行&#xff0c;感觉很麻烦。shell 脚本里面连接其他服务器用的就是 ssh 的方…

Oracle文件自动“减肥”记

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;我是【IT邦德】&#xff0c;江湖人称jeames007&#xff0c;10余年DBA及大数据工作经验 一位上进心十足的【大数据领域博主】&#xff01;&#x1f61c;&am…