protobuf编码和存储方式详解

一、消息结构

结论先行】:

  • protobuf将消息里的每个字段进行编码后,再利用T-L-V或者T-V的方式进行数据存储
  • protobuf对于不同类型的数据会使用不同的编码和存储方式
  • protobuf的编码和存储方式是其性能优越、数据体积小的原因

  protobuf中每一个消息中的字段都是key-value类型,序列化的时候key-value被编码和存储为字节流整体的消息结构如下图所示:
在这里插入图片描述
Tag 由字段编号 field_number 和 编码类型 wire_type 组成, Tag 整体采用 Varints 编码。Tag的值为(field_number << 3) | wire_type,即最后三位存储wire type(编码类型),其他位用于存储field number(字段编号)。
在这里插入图片描述
wire type编码有以下几种类型:
在这里插入图片描述
【T-L-V格式】:

  • T 作为标识号表明了在message中的位置以及数据类型。

  • L 定义了value的长度,表示可变长度的字段。

  • V 表示实际的value。

二、Wire Type = 0时的编码和存储方式

  结论先行】:对于int32/int64类型的数据(正数),protobuf会使用Varints编码;而对于sint32/sint64类型的数据(负数),protobuf会先使用ZigZag 编码,再使用Varints编码存储格式为Tag-Value

2.1 编码方式

2.1.1 Varints编码

  Varints是使用一个或多个字节序列化整数的一种方法,也是一种压缩算法在Varints编码方式中,数字越小,占用的字节越少。Varints编码的主要依据是:越小的数字出现的频率越高

  通常我们要存储一个整数需要4个字节或者8个字节,例如对于32位整数300,其二进制表示为:00000000 00000000 00000001 00101100。可以看到二进制表示中有很多无用的0也占用了内存,因此可以使用Varints编码对其进行压缩和解码。

  Varints编码使用msb表示当前是否是最后一个字节,如果msb为1表示后面还有字节,如果msb为0表示当前字节是最后一个字节。因为加入了msb,所以每一个字节低七位表示具体的数值

Varints编码过程如下】:

  1. 先将整数转换为不带前导0的二进制表示:100101100
  2. 每一个字节的低七位存储数值:0000010 0101100
  3. 转换为小端模式:0101100 0000010
  4. 添加msb标志位:10101100 00000010

  从上述编码过程可知,原来需要四个字节表示的整数经过编码后只需要两个字节即可存储。Varints编码由于使用了msb标志位,因此可以表示的最大整数为2^28,所以说Varints编码基本可以满足绝大多数的应用场景。

2.1.2 ZigZag编码

  Varints的不足在于对负数进行编码时效率极低,因为负数的二进制表示中1特别多,所以没办法去掉前导0,所以使用Varints编码意义不大。ZigZag编码被用来解决这一问题,其核心思想是将有符号整数转换为无符号整数,进而能够使用Varints编码如下图所示:
在这里插入图片描述
  以负数-11为例,其二进制在计算机中是用补码表示的,整数原码为:00001011,反码为:11110100,补码(反码加1)为:11110101

ZigZag编码过程如下】:

  1. 原数左移一位(左移时丢弃最高位,低位补0):11101010
  2. 原数右移31位(右移时符号位不变,高位补符号位):11111111
  3. 将上述两个二进制数取异或:00010101

  ZigZag编码和解码过程用代码表示为:

int int_to_zigzag(int n)
{
	return (n <<1) ^ (n >>31);
}

int zigzag_to_int(int n) 
{
	return (((unsignedint)n) >>1) ^ -(n & 1);
}

2.2 存储方式

  protobuf使用Varints和ZigZag编码后,以T-V格式存储数据,即:
在这里插入图片描述

三、Wire Type = 2时的编码和存储方式

  结论先行】:对于string,bytes和嵌套消息类型的数据,protobuf会使用Length-delimited编码存储格式为Tag-Length-Value

3.1 编码方式

  当时value的类型为string,bytes和嵌套消息时,protobuf使用Length-delimited编码,即将value的length也编码进最终数据。
例如:

message Test2 {
  required string b = 2;
}
// 设置b的值为"testing",则编码之后为:
12 07 74 65 73 74 69 6e 67

在这里插入图片描述

3.2 存储方式

  使用Length-delimited编码时的数据存储方式为T-L-V,即:
在这里插入图片描述

四、Wire Type = 1&5时的编码和存储方式

  结论先行】:对于大整数类型的数据,protobuf会使用64-bit和32-bit编码方式存储格式为Tag-Value

4.1 编码方式

  Varints适合处理一定范围内的数字,当数字很大的时候使用Varints编码效率反而很低,因此protobuf定义了64-bit和32-bit两种定长编码类型,即:
在这里插入图片描述

4.2 存储方式

  protobuf使用64-bit和32-bit编码后,以T-V格式存储数据

参考:
https://developers.google.cn/protocol-buffers/docs/encoding
https://www.bilibili.com/video/BV1Bt411X73L/?spm_id_from=333.788.videocard.19
https://www.cnblogs.com/mler/p/10252886.html
https://www.jianshu.com/p/30ef9b3780d9
https://www.jianshu.com/p/6dd2fd0362b8

  • 0
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

~青萍之末~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值