protocol buffer的高效编码方式
简介
protocol buffer这种优秀的编码方式,究竟底层是怎么工作的呢?为什么它可以实现高效快速的数据传输呢?这一切都要从它的编码方式说起。
定义一个简单的message
我们知道protocol buffer的主体就是message,接下来我们从一个简单的message出发,详细讲解protobuf中的编码方式。
比如下面的一个非常简单的消息对象:
message Student {
optional int32 age = 1;
}
在上面的例子中,我们定义了一个Student消息对象,并给他定义了一个名叫age的字段,并给它设置一个值叫做22。然后使用protobuf将其进行序列化,这么大的一个对象,对其序列化之后的字节如下所示:
08 96 00
很简单,使用三个字节就可以表示一个messag对象,数据量非常小。
那么这三个字节到底表示什么意思呢?一起来看看吧 。
Base 128 Varints
在解释上面的三个字节的含义之前,我们需要了解一个varints的概念。
什么叫Varints呢?就是序列化整数的时候,占用的空间大小是不一样的,小的整数占用的空间小,大的整数占用的空间大,这样不用固定一个具体的长度,可以减少数据的长度,但是会带来解析的复杂度。
那么怎么知道这个数据到底需要几个byte呢?在protobuf中,每个byte的最高位是一个判断位,如果这个位被置位1,则表示后面一个byte和该byte是一起的,表示同一个数,如果这个位被置位0,则表示后面一个byte和该byte没有关系,数据到这个byte就结束了。
举个例子,一个byte是8位,如果表示的是整数1,那么可以用下面的byte来表示:
0000 0001
如果一个byte装不下的整数,那么就需要使用多个byte来进行连接操作,比如下面的数据表示的是300:
1010 1100 0000 0010
为什么是300呢?首先看第一个byte,它的首位是1,表示后面还有一个byte。再看第二个byte,它的首位是0,表示到此就结束了。我们把判断位去掉,变成下面的数字:
010 1100 000 0010
这时候还不能计算数据的值,因为在protobuf中,byte的位数是反过来的,所以我们需要把上面的两个byte交换一下位置:
000 0010 010 1100
也就是:
10 010 1100
=256 + 32 + 8 + 4 = 300
消息体的结构
从message的定义可以知道,protobuf中的消息体的结构是key=value的形式,其中的key就是message中定义的字段的整数值1,2,3,4等。而value就是真正对其设置的值。
当一个消息被编码之后,这些key和value会被连接在一起,组成一个byte stream。当要对其进行解析的时候,需要定位到key和value的具体长度,所以在key中需要包含两部分,第一个部分就是字段在proto文件中的值,第二个部分就是value部分占用的长度大小。
只有通过这两个部分的值结合起来,解析器才能够正确的对字段进行解析。
key的这种格式,被称为 wire types,有哪些 wire types呢?我们看一下:
| 类型 | 含义 | 使用场景 |
|---|---|---|
| 0 | Varint | int32, int64, uint32, uint64, sint32, sint64, bool, enum |
| 1 | 64-bit | fixed64, sfixed64, double |
| 2 | Length-delimited | string, bytes, embedded messages, packed repeated fields |
| 3 | Start group | groups (deprecated) |
| 4 | End group | groups (deprecated) |
| 5 | 32-bit | fixed32, sfixed32, float |
可以看到除了3,4两种类型之外,其他的类型可以分为三类,一类是固定长度的类型,如1,5,他们分别是64位和32位的数字。
第二类是0,表示Varint,这是一种可变类型,用来表示通用的数字类型,bool类型和枚举类型。第三类2,表示长度区分的类型,这种类型通常用来表示字符串,字节数字等。
所有的key都是一个varint类型,它的值是:(field_number << 3) | wire_type ,也就是说key的最后三个位,用来存储wire类型。
上面我们例子中的key的值是08,用二进制表示:
000 1000
最后三位是0,表示是一个Varint类型,将08右移三位,得到1,表示key表示的字段是1这个字段,也就是age。
然后我们看下剩下的部分96 00,换成二进制是:
96 00 = 1001 0110 0000 0000
根据Varint的定义,第一位表示的是连接位,表示第二个字节的内容和第一个字节的内容是一起的。对于Varint来说,需要将低位的字节和高位的字节进行交换,如下:
1001 0110 0000 0000 去掉最高位的1 :
001 0110 0000 0000 交换低位字节和高位字节:
0000 0000 001 0110
上面的值是16 + 4 + 2 = 22
这样我们就得到了值为1的key,对应的value是22。
符号整数
我们知道有两种表示符号整数的方式,一种是标准的int类型:int32 和 int64,一种是带符号的int类型:sint32 和 sint64。
这两种类型的区别在于对应负整数的表示上。对于int32和int64来说,所有的负整数都是以十个字节来表示的,所以占用的空间会比较大,不适合用来表示负整数。
如果使用sint32 和 sint64,那么使用的编码方式是ZigZag,对于负整数来说更加有效。
ZigZag将带符号的整数和无符号的整数进行映射,对于每个n来说,将会使用下面的公式来编码:
(n << 1) ^ (n >> 31)
对于sint64来说就是:
(n << 1) ^ (n >> 64)
举个例子:
| 符号整数 | 编码结果 |
|---|---|
| 0 | 0 |
| -1 | 1 |
| 1 | 2 |
| -2 | 3 |
| 2147483647 | 4294967294 |
| -2147483648 | 4294967295 |
字符串
字符串的wire类型是2,说明它的值是一个varint编码的长度。举个例子:
message Student {
optional string name = 2;
}
上我们给Student定义了第二个属性name,假如给name赋值 "testing" ,那么得到的编码是:
12 07 [74 65 73 74 69 6e 67]
中括号的编码就是"testing"的UTF8表示。
0x12 可以这样解析:
0x12
→ 0001 0010 (binary representation)
→ 00010 010 (regroup bits)
→ field_number = 2, wire_type = 2
0x12表示字段2的类型是2,后面跟着的07就表示后续byte字节的长度了。
嵌套的消息
消息中可以嵌套消息,我们看一个例子:
message Teacher {
optional Student s = 3;
}
假如我们把s的age字段设置为22,就和第一个例子一样,那么上面的编码就是:
1a 03 08 96 00
可以看到后面的三个字节和第一个例子是一样的。前面两个字节的判断方式和字符串是一值的,这样就不再多讲。
总结
好了,protobuf的基本编码规则和实现已经讲完了。听起来是不是很奇妙?
本文已收录于 http://www.flydean.com/03-protobuf-encoding/
最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!
欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!
protocol buffer的高效编码方式的更多相关文章
- google protocol buffer——protobuf的编码原理二
这一系列文章主要是对protocol buffer这种编码格式的使用方式.特点.使用技巧进行说明,并在原生protobuf的基础上进行扩展和优化,使得它能更好地为我们服务. 在上一篇文章中,我们主要通 ...
- Protocol Buffer 序列化原理大揭秘 - 为什么Protocol Buffer性能这么好?
前言 习惯用 Json.XML 数据存储格式的你们,相信大多都没听过Protocol Buffer Protocol Buffer 其实 是 Google出品的一种轻量 & 高效的结构化数据存 ...
- 快来看看Google出品的Protocol Buffer,别只会用Json和XML了
前言 习惯用 Json.XML 数据存储格式的你们,相信大多都没听过Protocol Buffer Protocol Buffer 其实 是 Google出品的一种轻量 & 高效的结构化数据存 ...
- 快来看看Google出品的Protocol Buffer,别仅仅会用Json和XML了
前言 习惯用 Json.XML 数据存储格式的你们,相信大多都没听过Protocol Buffer Protocol Buffer 事实上 是 Google出品的一种轻量 & 高效的结构化数据 ...
- google protocol buffer——protobuf的基本使用和模型分析
这一系列文章主要是对protocol buffer这种编码格式的使用方式.特点.使用技巧进行说明,并在原生protobuf的基础上进行扩展和优化,使得它能更好地为我们服务. 1.什么是protobuf ...
- Protocol Buffer序列化Java框架-Protostuff
了解Protocol Buffer 首先要知道什么是Protocol Buffer,在编程过程中,当涉及数据交换时,我们往往需要将对象进行序列化然后再传输.常见的序列化的格式有JSON,XML等,这些 ...
- Google Protocol Buffer 的编码方式
Google Protocol Buffer 使用到了两种编码方式:Varints 和 zigzag. 一 Varints 编码 每个 byte 只用 7bit 表示数字,最高位 bit作为标志位,如 ...
- google protocol buffer——protobuf的使用特性及编码原理
这一系列文章主要是对protocol buffer这种编码格式的使用方式.特点.使用技巧进行说明,并在原生protobuf的基础上进行扩展和优化,使得它能更好地为我们服务. 在上一篇文章中,我们展示了 ...
- protocol buffer 编码
protocol buffer能够跨平台提供轻量的序列化和反序列化,得益于其平台无关的编码格式,本文就介绍下其中的编码格式. Varints 在protocol buffer中大量使用到了Varint ...
随机推荐
- javascript数组 (转)
javascript的Array可以包含任意数据类型,并通过索引来访问每个元素. 要取得Array的长度,直接访问length属性: var arr = [1,2,3.14,'Hell0' ...
- Java实验项目三——面向对象定义职工类和日期类
Program:按照如下要求设计类: (1)设计一个日期类,用于记录年.月.日,并提供对日期处理的常用方法. (2)设计一个职工类,该职工类至少具有下面的属性:职工号,姓名,性别,生日,工作部门,参加 ...
- 小程序textarea设置maxlength后不是你想的那样简单
可能很多小伙伴们.看见这个标题后; 觉得作者是一个标题党. textarea设置maxlength后, 限制用户输入的字符呗! 还能怎么样呢? 恭喜你,说对了一半. 之前我也一直是这样想的. 知道今天 ...
- 阿里云低延时直播 RTS 能力升级 让直播推流效果更佳
行业背景 直播技术飞速发展让各个行业的用户体验呈现多样化和个性化,不同业务场景下创新实践满足大众对于音视频互动体验和参与的高标准要求.历经2020年初的巨变之后,以视频.游戏.电商.教育为主的互联网经 ...
- JAVA中自增自减运算符(i++与++i的区别)
注意: 自增运算符和自减运算符只能用于变量,而不能用于常亮或表达式 运算符 运算 范例 结果 ++ 自增(前):先运算后取值 a=2;b=++a; a=3;b=3; ++ 自增(后):先取值后运算 a ...
- React组件三大属性之 refs
React组件三大属性之 refs refs属性 1) 组件内的标签都可以定义ref属性来标识自己 a. <input type="text" ref={input => ...
- PAT甲级:1066 Root of AVL Tree (25分)
PAT甲级:1066 Root of AVL Tree (25分) 题干 An AVL tree is a self-balancing binary search tree. In an AVL t ...
- 手把手教你实现Android编译期注解
一.编译期注解在开发中的重要性 从早期令人惊艳的ButterKnife,到后来的以ARouter为首的各种路由框架,再到现在谷歌大力推行的Jetpack组件,越来越多的第三方框架都在使用编译期注解这门 ...
- 秒懂 Java 的三种代理模式
前言 代理(Proxy)模式是一种结构型设计模式,提供了对目标对象另外的访问方式:即通过代理对象访问目标对象. 这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能. ...
- POJ1417 True Liars 题解
通过读题,容易发现,当回答为yes时 \(x,y\) 必属于同类,当回答为no时二者必为异类(并且当 \(x=y\) 时,回答必为yes,不过这题不用这个性质). 于是先按关系维护连通块,然后求出每个 ...