Protobuf编码规则
支持类型
该表显示了在 .proto
文件中指定的类型,以及自动生成的类中的相应类型:
.proto Type | Notes | C++ Type | Java/Kotlin Type[1] Java/Kotlin 类型 [1] | Python Type[3] | Go Type | Ruby Type | C# Type | PHP Type | Dart Type | |
---|---|---|---|---|---|---|---|---|---|---|
double | double | double | float | float64 | Float | double | float | double | ||
float | float | float | float | float32 | Float | float | float | double | ||
int32 | varint编码。对于负数编码效率低下——如果字段可能有负值,建议改用 sint32。 | int32 | int | int | int32 | Fixnum or Bignum (as required) | int | integer | int | |
int64 | varint编码。对于负数编码效率低下——如果字段可能有负值,建议改用 sint64。 | int64 | long | int/long | int64 | Bignum | long | integer/string | Int64 | |
uint32 | varint编码。 | uint32 | int | int/long | uint32 | Fixnum or Bignum (as required) | uint | integer | int | |
uint64 | varint编码。 | uint64 | long | int/long | uint64 | Bignum | ulong | integer/string | Int64 | |
sint32 | zigzag和varint编码。有符号的 int 值。比常规的 int32 能更高效地编码负数。 | int32 | int | int | int32 | Fixnum or Bignum (as required) ) | int | integer | int | |
sint64 | zigzag和varint编码。有符号的 int 值。比常规的 int64 能更高效地编码负数。 | int64 | long | int/long | int64 | Bignum | long | integer/string | Int64 | |
fixed32 | 总是四个字节。如果值通常大于 2\(^{28}\) ,则比 uint32 更有效。 | uint32 | int | int/long | uint32 | Fixnum or Bignum (as required) | uint | integer | int | |
fixed64 | 总是八个字节。如果值通常大于 2\({^56}\) ,则比 uint64 更有效。 | uint64 | long | int/long | uint64 | Bignum | ulong | integer/string | Int64 | |
sfixed32 | 总是四个字节。 | int32 | int | int | int32 | Fixnum or Bignum (as required) | int | integer | int | |
sfixed64 | 总是八个字节。 | int64 | long | int/long | int64 | Bignum | long | integer/string | Int64 | |
bool | bool | boolean | bool | bool | TrueClass/FalseClass | bool | boolean | bool | ||
string | 字符串必须始终包含 UTF-8 编码或 7 位 ASCII 文本,并且不能长于 2\(^32\) 。 | string | String | str/unicode | string | String (UTF-8) | string | string | String | |
bytes | 可以包含任何不超过 2\(^{32}\) 的任意字节序列。 | string | ByteString | str (Python 2) bytes (Python 3) | []byte | String (ASCII-8BIT) | ByteString | string | List |
消息结构
对于传统的 xml 或者 json 等方式的序列化中,编码时直接将 key 本身加进去,例如:
{
"foo": 1,
"bar": 2
}
这样最大的好处就是可读性强,但是缺点也很明显,传输效率低,每次都需要传输重复的字段名。Protobuf 使用了另一种方式,将每一个字段进行编号,这个编号被称为 field number
。通过 field_number
的方式解决 json 等方式重复传输字段名导致的效率低下问题,例如:
message {
int32 foo = 1;
string bar = 2;
}
field_number
的类型被称为wire types,目前有六种类型:VARINT
, I64
, LEN
, SGROUP
, EGROUP
, and I32
(注:类型3和4已废弃),因此需要至少3位来区分:
ID | Name | Used For |
---|---|---|
0 | VARINT | int32, int64, uint32, uint64, sint32, sint64, bool, enum |
1 | I64 | fixed64, sfixed64, double |
2 | LEN | string, bytes, embedded messages, packed repeated fields |
3 | SGROUP | group start (deprecated) |
4 | EGROUP | group end (deprecated) |
5 | I32 | fixed32, sfixed32, float |
当 message 被编码时,每一个 key-value 包含 <tag> <type> <paylog>
,其结构如下:
+--------------+-----------+---------+
| field_number | wire_type | payload |
+--------------+-----------+---------+
| | |
| | | +---------------+
+---------------+ +--------->| (length) data |
| tag | +---------------+
+---------------+
- field_number 和 wire_type 被称为 tag,使用一个字节来表示(这里指编码前的一个字节,通过Varint编码后可能并非一个字节)。其值为
(field_number << 3) | wire_type
,换句话说低3位解释了wire_type,剩余的位则解释了field_number。 - payload 则为 value 具体值,根据 wire_type 的类型决定是否是采用 Length-Delimited 记录
额外一提的是由于 tag 结构如上所述,因此对于使用 Varint 编码的 1个字节来说去除最高位标志位和低三位保留给 wire_type使用,剩下四位能够表示[0, 15] 的字段标识,超过则需要使用多于一个字节来存储 tag 信息,因此尽可能将频繁使用的字段的字段标识定义在 [0, 15] 直接。
编码规则
Protobuf 使用一种紧凑的二进制格式来编码消息。编码规则包括以下几个方面:
- 每个字段都有一个唯一的标识符和一个类型,标识符和类型信息一起构成了字段的 tag。
- 字段的 tag 采用 Varint 编码方式进行编码,可以节省空间。
- 字符串类型的字段采用长度前缀方式进行编码,先编码字符串的长度,再编码字符串本身。
- 重复的字段可以使用 repeated 关键字进行定义,编码时将重复的值按照顺序编码成一个列表。
Varint 编码
Varint 是一种可变长度的编码方式,可以将一个整数编码成一个字节序列。值越小的数字,使用越少的字节数表示。它的原理是通过减少表示数字的字节数从而实现数据体积压缩。
Varint 编码的规则如下:
- 对于值小于 128 的整数,直接编码为一个字节;
- 对于值大于等于 128 的整数,将低 7 位编码到第一个字节中,将高位编码到后续的字节中,并在最高位添加一个标志位(1 表示后续还有字节,0 表示当前字节是最后一个字节)。每个字节的最高位也称 MSB(most significant bit)。
在解码的时候,如果读到的字节的 MSB 是 1 话,则表示还有后序字节,一直读到 MSB 为 0 的字节为止。
例如,int32类型、field_number为1、值位 300 的 Varint 编码为:
// 300 的二进制
00000001 00101100
// 按7位切割
00 0000010 0101100
// 高位全0省略
0000010 0101100
// 逆序,使用的小端字节序
0101100 0000010
// 每一组加上msb,除了最后一组是msb是0,其他的都为1
10101100 00000010
// 十六进制指
ac 02
// 按照 protobuf 的消息结构,其完整位
08 ac 02
| |__|__ payload
|
|----------- tag (field-number << 3 | wire-type) = (1 << 3 | 0) = 0x08
ZigZag编码
对于 int32/int64
的 proto type,值大于 0 时直接使用 Varint 编码,而值为负数时做了符号拓展,转换为 int64
的类型,再做 Varint 编码。负数高位为1,因此对于负数固定需要十个字节( ceil(64 / 7) = 10
)。(这里有个值得思考的问题是对于 int32 类型的负数为什么要转换为 int64
来处理?不转换的话使用5个字节就能够完成编码了。网上的一个说法是为了转换为 int64 类型时没有兼容性问题,此处由于还未阅读过源码,不知道内部是怎么处理的,因此暂时也没想通为什么因为兼容性问题需要做符号拓展。因为按照 Varint 编码规则解码的话,直接读取出来的值赋值给 int64 的类型也没有问题。int32 negative numbers)
很明显,这样对于负数的编码是非常低效的。因此 protobuf 引入 sint32
和 sint64
,在编码时先将数字使用 ZigZag
编码,然后再使用 Varint
编码。
ZigZag 编码将有符号数映射为无符号数,对应的编解码规则如下:
static uint32_t ZigZagEncode32(int32_t v) {
// Note: the right-shift must be arithmetic
// Note: left shift must be unsigned because of overflow
return (static_cast<uint32_t>(v) << 1) ^ static_cast<uint32_t>(v >> 31);
}
static uint64_t ZigZagEncode64(int64_t v) {
// Note: the right-shift must be arithmetic
// Note: left shift must be unsigned because of overflow
return (static_cast<uint64_t>(v) << 1) ^ static_cast<uint64_t>(v >> 63);
}
int32_t ZigZagDecode32(uint32_t n) {
// Note: Using unsigned types prevent undefined behavior
return static_cast<int32_t>((n >> 1) ^ (~(n & 1) + 1));
}
static int64_t ZigZagDecode64(uint64_t n) {
// Note: Using unsigned types prevent undefined behavior
return static_cast<int64_t>((n >> 1) ^ (~(n & 1) + 1));
}
因此如果传输的数据中可能包含有负数,那么应该使用 sint32/sint64
类型。因为 protobuf 中只定义了为这两种数据类型进行 ZigZag
编码再使用 Varint
编码。
Length-delimited 编码
wire_type
为 LEN
,由于其具有动态长度,因此其由一个 Length 值保存长度大小,这个 Length 同样通过 Varint 编码,最后是其内容。
参照以下例子:
message Test2 {
optional string b = 2;
}
b = "testing"
12 07 [74 65 73 74 69 6e 67]
| | t e s t i n g
| | |__|__|__|__|__|__ body 的 ASCII 码
| |
| |__ length = 6 = 0x06
|
|__ Tag (field-number << 3 | wire-type) = (2 << 3 | 2) = 18 = 0x12
Protobuf编码规则的更多相关文章
- protobuf中的编码规则
protobuf中的编码规则 (1)序列化和反序列化: 在开始本部分的内容之前,首先有必要介绍两个基本概念,一个是序列化,一个是反序列化.这两个概念的定义在网上搜一下都很多的,但大多都讲得比较晦涩,不 ...
- protobuf编码
proto2 Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构化数据序列化,适合做数据存储或 RPC 数据交换格式.可用于通讯协议.数据存储等领域的语言无关.平台无 ...
- UTF-8编码规则(转)
from:http://www.cnblogs.com/chenwenbiao/archive/2011/08/11/2134503.html UTF-8是Unicode的一种实现方式,也就是它的字节 ...
- 转:从开源项目学习 C 语言基本的编码规则
从开源项目学习 C 语言基本的编码规则 每个项目都有自己的风格指南:一组有关怎样为那个项目编码约定.一些经理选择基本的编码规则,另一些经理则更偏好非常高级的规则,对许多项目而言则没有特定的编码规则,项 ...
- 通用的业务编码规则设计实现[转:http://www.cnblogs.com/xqin/p/3708367.html]
一.背景 每一个企业应用中不可避免的都会涉及到业务编码规则的问题,比如订单管理系统中的订单编号,比如商品管理系统中的商品编码,比如项目管理系统中的项目编码等等,这一系列的编码都需要管理起来,那么它们的 ...
- UTF-8编码规则
UTF-8是Unicode的一种实现方式,也就是它的字节结构有特殊要求,所以我们说一个汉字的范围是0X4E00到0x9FA5,是指unicode值,至于放在utf-8的编码里去就是由三个字节来组织,所 ...
- BASE64编码规则及C#实现
一.编码规则 Base64编码的思想是是采用64个基本的ASCII码字符对数据进行重新编码.它将需要编码的数据拆分成字节数组.以3个字节为一组.按顺序排列24位数据,再把这24位数据分成4组 ...
- openssl ans.1编码规则分析及证书密钥编码方式
1 数据编码格式 openssl的数据编码规则是基于ans.1的,ans.1是什么 ? 先上高大上的解释 ASN.1(Abstract Syntax Notation One), 是一种结构化的描述语 ...
- UTF-8编码规则【转】
hz_chenwenbiao UTF-8编码规则(转) UTF-8是Unicode的一种实现方式,也就是它的字节结构有特殊要求,所以我们说一个汉字的范围是0X4E00到0x9FA5,是指unicode ...
- protobuf 编码实现解析(java)
一:protobuf编码基本数据类型 public enum FieldType { DOUBLE (JavaType.DOUBLE , WIRETYPE_FIXED64 ), FLOAT (Java ...
随机推荐
- vue+iview 表格行选中修改背景色
<Table :columns="columns" :no-data-text="L('NoDatas')" border :data="lis ...
- HBase启动HMaster闪退
1.问题描述 (1)HBase启动 [Hadoop@master conf]$ start-hbase.sh SLF4J: Class path contains multiple SLF4J bin ...
- ssh反向代理树莓派+motion,实现公网远程视频监控
注意:本文公网远程监控部分需要借助有公网IP的云服务器进行ssh反向代理. 一.借助motion实现内网的视频监控 准备 插上摄像头,然后输入ls /dev/video*命令检查是否识别了摄像头 安装 ...
- jmeter之阶段式压测
一.bzm - Concurrency Thread Group 1.什么是阶梯式压测 阶梯式压测,就是对系统的压力呈现阶梯性增加的过程,每个阶段压力值都要增加一个数量值,最终达到一个预期值.然后保持 ...
- c#获取文本中的内容
string path = HttpContext.Current.Server.MapPath("/文件夹/名称.txt"); string ss = File.ReadAllT ...
- React16下报错引发整个页面crash的解决方法
如果报错没有没有被catch,将会引起整个React组件树的unmounting 解决方法:在生命周期中增加componentDidCatch https://reactjs.org/blog/201 ...
- RBAC学习(一)
0.前提 :用户只有一个直属部门,但角色可以关联多个部门 有一种情况就不太适用:比如说地区经理是一个角色,张三是北京市地区经理,他在组织架构中的直属部门是华北大区,然后一个黑龙江的销售李四提一个折扣申 ...
- Spring AOP——源码分析
[阅读前提]:需了解 AOP 注解开发流程:链接 一.注解 @EnableAspectJAutoProxy 在配置类中添加注解@EnableAspectJAutoProxy,便开启了 AOP(面向切面 ...
- TCC 分布式事务解决方案
更多内容,前往 IT-BLOG 一.什么是 TCC事务 TCC 是Try.Confirm.Cancel三个词语的缩写,TCC要求每个分支事务实现三个操作:预处理Try.确认Confirm.撤销Canc ...
- 【数仓运维实践】关于GaussDB(DWS)单SQL磁盘空间管控
摘要:本文主要讲解数仓运维中遇到单SQL磁盘空间管控问题的解析和方案. 本文分享自华为云社区<GaussDB(DWS)运维 -- 单SQL磁盘空间管控>,作者: 譡里个檔. [问题描述] ...