Protobuf 编码结构

编码结构
什么是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,不带packedrepeated int32 cat = 1;// 方式2,带packedrepeated  int32 dog = 2 [packed=true];
}
的 repeated 字段存储方式,即相同的 tag 只存储一次,添加 repeated 字段下所有值的长度 length,构成 TLVVV... 存储结构,可以压缩序列化后数据长度,节省传输开销。复制代码```go
message repeatedEncodeTest{// 方式1,不带packedrepeated int32 cat = 1;// 方式2,带packedrepeated  int32 dog = 2 [packed=true];
}

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

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

相关文章

MySQL-数据库概述

数据库相关概念&#xff1a; 数据库(DateBase)简称DB,就是一个存储数据的仓库&#xff0c;数据有组织的进行存储。 数据库分为关系型数据库简称RDBMS和非关系型数据库 关系型数据库简称RDBMS:建立在关系模型的基础上&#xff0c;由多张相互连接的二维表组成的数据库.简单来说…

Nginx(十七) 日志轮询/切割

1.编写shell脚本 Nginx_Log_Path"/usr/local/nginx/logs/" Dateformat$(date -d "yesterday" %Y%m%d) mv ${Nginx_Log_Path}/access.log ${Nginx_Log_Path}/access-${Dateformat}.log mv ${Nginx_Log_Path}/access_8688.log ${Nginx_Log_Path}/access_868…

【Linux软件包管理器】yum详解

目录 1、什么是软件包 2、yum的操作 1&#xff09;yum源 2&#xff09;三板斧 ① yum list ② yum install [软键名] ③ yum remove [软件名] 1、什么是软件包 在Linux下安装软件, 一个通常的办法是下载到程序的源代码, 并进行编译, 得到可执行程序. 但是这样太麻烦了,…

除了sd webui,compfy还有一个sd UI

GitHub - VoltaML/voltaML-fast-stable-diffusion: Beautiful and Easy to use Stable Diffusion WebUI

国科大图像处理2024速通期末——汇总2017-2019、2023回忆

国科大2023.12.28图像处理0854期末重点 图像处理 王伟强 作业 课件 资料 一、填空 一个阴极射线管它的输入与输出满足 s r 2 sr^{2} sr2&#xff0c;这将使得显示系统产生比希望的效果更暗的图像&#xff0c;此时伽马校正通常在信号进入显示器前被进行预处理&#xff0c;令p…

Unity之键盘鼠标的监控

小编最近在玩大表哥2&#xff0c;通过 W、A、S、D 来移动亚瑟&#xff0c;鼠标左键来不吃牛肉 我们都知道玩家通过按键鼠标来控制游戏人物做出相应的行为动作&#xff0c;那在Unity引擎里是怎么知道玩家是如何操作的呢&#xff1f;本篇来介绍Unity是怎样监控键盘和鼠标的。 首先…

fatal: Need to specify how to reconcile divergent branches.如何处理

错误信息 “fatal: Need to specify how to reconcile divergent branches.” 通常在尝试推送到远程仓库时出现&#xff0c;尤其是当本地分支和远程分支有分歧&#xff08;即它们各自有一些不同的提交&#xff09;时。处理这个问题通常涉及合并&#xff08;merge&#xff09;或…

计算机网络期末知识汇总

一、计算机网络概述 1.Internet 的中文译名并不统一。 现有的 Internet 译名有两种&#xff1a; 因特网&#xff0c;这个译名是全国科学技术名词审定委员会推荐的&#xff0c;但却长期未得 到推广&#xff1b; 互联网&#xff0c;这是目前流行最广的、事实上的标准译名。现…

spring常用注解(三)springbean类

一、Service用于标注业务层组件、 二、Repository用于标注数据访问组件&#xff0c;即DAO组件。 三、Component泛指组件&#xff0c;当组件不好归类的时候&#xff0c;我们可以使用这个注解进行标注。&#xff08;pojo&#xff09; 四、Scope用于指定scope作用域的&#xff…

Node.js + Mysql 防止sql注入的写法

关键代码 const queryString SELECT * FROM sys_user LIMIT ?, ?;let data await query(queryString, [startIndex,pageSize]); 访问数据库相关代码 const mysql require(mysql)const pool mysql.createPool({host: 127.0.0.1,user: root,password: 123456,database:…

【C语言】静动态内存的跨函数访问malloc、free

目录 多指针初认识&#xff1a;动态内存和多级指针的跨函数访问动态内存和静态内存的比较&#xff1a;静态内存不可以跨函数访问&#xff1a;动态内存跨函数访问&#xff1a;malloc和free示例 多指针初认识&#xff1a; #include <stdio.h> #include <stdlib.h> in…

技术查漏补缺(1)Logback

一、下定义&#xff1a;Logback是一个开源的日志组件 二、Logback的maven <!--这个依赖直接包含了 logback-core 以及 slf4j-api的依赖--> <dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><v…

新手解锁语言之力:理解 PyTorch 中 Transformer 组件

目录 torch.nn子模块transformer详解 nn.Transformer Transformer 类描述 Transformer 类的功能和作用 Transformer 类的参数 forward 方法 参数 输出 示例代码 注意事项 nn.TransformerEncoder TransformerEncoder 类描述 TransformerEncoder 类的功能和作用 Tr…

vite + vue3引入ant design vue 报错

npm install ant-design-vue --save下载插件并在main.ts 全局引入 报错 解决办法一&#xff1a; main.ts注释掉全局引入 模块按需引入 解决办法二 将package.json中的ant-design-vue的版本^4.0.0-rc.4改为 ^3.2.15版本 同时将将package-lock.json中的ant-design-vue的版本…

Android13 热点默认5G频道配置修改

Android13 热点默认5G频道配置修改 文章目录 Android13 热点默认5G频道配置修改一、前言二、修改默认配置1、代码中修改默认配置2、保存默认配置文件设置默认5G频段配置热点配置文件完整信息示例&#xff1a; 3、代码中强制设置配置信息&#xff08;1&#xff09;在关键流程设置…

Graceful Response 构建 Spring Boot 下优雅的响应处理

一、Graceful Response Graceful Response 是一个 Spring Boot 技术栈下的优雅响应处理器&#xff0c;提供一站式统一返回值封装、全局异常处理、自定义异常错误码等功能&#xff0c;使用Graceful Response进行web接口开发不仅可以节省大量的时间&#xff0c;还可以提高代码质…

小白综述:深度学习 OCR 图片文字识别

文章目录 1. OCR 算法流程1.1 传统 OCR 方法1.2 深度学习 OCR 方法1.2.1 two-stage方法&#xff1a;文字检测识别1.2.2 端到端方法 2. 文本检测算法3. 文本识别算法3.1 基于分割的单字符识别方法3.2 基于序列标注的文本行识别方法 1. OCR 算法流程 OCR (Optical Character Rec…

揭开 JavaScript 作用域的神秘面纱(上)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

简单vlan划分和dhcp中继(Cisco Packet Tracer模拟)

文章目录 1. 前言2. 功能实现2.1. dhcp服务器接入2.2. 学校web服务器2.3. 设置学校dns服务器2.4. 设置线路冗余2.5. 配置ac。 1. 前言 在这里我们的计网作业是使用思科的Cisco Packet Tracer进行对校园网的简单规划&#xff0c;这里我对校园网进行了简单的规划&#xff0c;功能…

前端页面的生命周期

性能问题呈现给用户的感受往往就是简单而直接的&#xff1a;加载资源缓慢、运行过程卡顿或响应交互延迟等。而在前端工程师的眼中&#xff0c;从域名解析、TCP建立连接到HTTP的请求与响应&#xff0c;以及从资源请求、文件解析到关键渲染路径等&#xff0c;每一个环节都有可能因…