支持类型

该表显示了在 .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,目前有六种类型:VARINTI64LENSGROUPEGROUP, 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 引入 sint32sint64,在编码时先将数字使用 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_typeLEN,由于其具有动态长度,因此其由一个 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编码规则的更多相关文章

  1. protobuf中的编码规则

    protobuf中的编码规则 (1)序列化和反序列化: 在开始本部分的内容之前,首先有必要介绍两个基本概念,一个是序列化,一个是反序列化.这两个概念的定义在网上搜一下都很多的,但大多都讲得比较晦涩,不 ...

  2. protobuf编码

     proto2 Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构化数据序列化,适合做数据存储或 RPC 数据交换格式.可用于通讯协议.数据存储等领域的语言无关.平台无 ...

  3. UTF-8编码规则(转)

    from:http://www.cnblogs.com/chenwenbiao/archive/2011/08/11/2134503.html UTF-8是Unicode的一种实现方式,也就是它的字节 ...

  4. 转:从开源项目学习 C 语言基本的编码规则

    从开源项目学习 C 语言基本的编码规则 每个项目都有自己的风格指南:一组有关怎样为那个项目编码约定.一些经理选择基本的编码规则,另一些经理则更偏好非常高级的规则,对许多项目而言则没有特定的编码规则,项 ...

  5. 通用的业务编码规则设计实现[转:http://www.cnblogs.com/xqin/p/3708367.html]

    一.背景 每一个企业应用中不可避免的都会涉及到业务编码规则的问题,比如订单管理系统中的订单编号,比如商品管理系统中的商品编码,比如项目管理系统中的项目编码等等,这一系列的编码都需要管理起来,那么它们的 ...

  6. UTF-8编码规则

    UTF-8是Unicode的一种实现方式,也就是它的字节结构有特殊要求,所以我们说一个汉字的范围是0X4E00到0x9FA5,是指unicode值,至于放在utf-8的编码里去就是由三个字节来组织,所 ...

  7. BASE64编码规则及C#实现

    一.编码规则      Base64编码的思想是是采用64个基本的ASCII码字符对数据进行重新编码.它将需要编码的数据拆分成字节数组.以3个字节为一组.按顺序排列24位数据,再把这24位数据分成4组 ...

  8. openssl ans.1编码规则分析及证书密钥编码方式

    1 数据编码格式 openssl的数据编码规则是基于ans.1的,ans.1是什么 ? 先上高大上的解释 ASN.1(Abstract Syntax Notation One), 是一种结构化的描述语 ...

  9. UTF-8编码规则【转】

    hz_chenwenbiao UTF-8编码规则(转) UTF-8是Unicode的一种实现方式,也就是它的字节结构有特殊要求,所以我们说一个汉字的范围是0X4E00到0x9FA5,是指unicode ...

  10. protobuf 编码实现解析(java)

    一:protobuf编码基本数据类型 public enum FieldType { DOUBLE (JavaType.DOUBLE , WIRETYPE_FIXED64 ), FLOAT (Java ...

随机推荐

  1. 用反证法说明List<Object>和List<String>不存在子父类关系可行吗?

    看宋红康老师的Java基础视频讲解,视频中用反证法证明List

  2. 替换yum源

    1.yum源进行备份 进入到yum源的配置文件中 执行命令如下:cd /etc/yum.repos.d 将yum源进行备份:mv Centos-Base.repo Centos-Base.repo.b ...

  3. UIPath踩坑记一 对 COM 组件的调用返回了错误 HRESULT E_FAIL。UiPath.UiNodeClass.InjectAndRunJS

    [ERROR] [UiPath.Studio] [1] 错误: System.Exception: 对 COM 组件的调用返回了错误 HRESULT E_FAIL. ---> System.Ex ...

  4. [转]sublime text 4注册

    1.打开浏览器进入网站https://hexed.it2.打开sublime text4安装目录选择文件sublime_text.exe3.搜索80 78 05 00 0f 94 c1更改为c6 40 ...

  5. linux 查看进程的启动开始时间

    先使用命令查看需要查看的进程 ps -ef | grep java root 29861 13755 2 09:42 pts/0 00:10:48 java -jar XXXX.jar ps axo ...

  6. 教你如何用纯css代码实现太极阴阳鱼动画效果

    今天看到一个有意思的效果,闲来无事做一个: 把2d静态的太极图改成了3d,阴极和阳极分到了两个平面里实现旋转效果,这个好实现,重点是实现它的透明效果,平面太极图显示出两极是用另加的块元素挡住底面的颜色 ...

  7. 最大流基础(Maximum Flow Basis)

    1. 最大流问题定义 1.1 流网络(Flow network) Def. A flow network is a tuple \(G = (V, E, s, t, c)\): Digraph \(( ...

  8. 【超详细】Ubuntu 20.04 安装 Apache+PHP网页环境 图文教程,常见问题和解决方案

    本文将介绍在Ubuntu20.04 LTS环境下安装Apache的全过程,针对其中可能出现的一些坑也会提供解决方案. 作者:Eriktse 简介:19岁,211计算机在读,现役ACM银牌选手力争以通俗 ...

  9. 关于Docker compose值IP与域名的映射 之 extra_host

    公司的所有项目都是采用Docker容器化部署,最近有一个项目需要使用定时任务调用第三方Api,正式web环境服务器的网络与第三方网络是通畅的,但是当将代码发布到正式环境,调用接口却显示 System. ...

  10. 浅谈ChatGPT如何取代前端开发工程师

    1.ChatGPT 是什么? ChatGPT 是一种基于深度学习的自然语言处理技术,它可以生成高质量的自然语言文本.该技术是由 OpenAI 团队 开发,旨在使计算机能够像人类一样理解和产生自然语言. ...