#### protobuf数据序列化与json或xml数据序列化的传输效率的对比 ####

本文深入探讨了Protobuf如何通过去除冗余信息、采用自描述方式记录数据类型、顺序及内容,实现比JSON和XML更高效的传输。文章详细介绍了Protobuf的六大wiretype及其应用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转自:https://juejin.cn/post/7034046510643806238

仅做个人查阅及备份用途,浏览请看原文

一句话概括:之所以Protobuf 序列化的数据会比 JSON、XML 传输效率更高,是因为Protobuf 序列化时去掉了无用数据致使压缩后数据更小。

目录

看表面

删除 ”结构“

自描述

Protobuf

1. 类型和顺序

2. Varint

3. Length-delimited

4. 浮点数

5. sint32/sint64

小结

今天,我带大家更深层次的认识认识 Protobuf,如果你对 Protobuf 的用法还不熟悉,直接前往:developers.google.com/protocol-bu…

当对 Protobuf 有了基本的认识后,就会明白了 Protobuf 序列化的数据会比 JSON、XML 传输效率更高。

那为啥会高呢?本篇就带着这个问题一探究竟。

看表面

对于 JSON、XML,为了便于数据传输时的可阅读性,会保留数据的结构化信息,举个 JSON 例子,如下:

{
  "name": "laomiao",
  "age": 18
}
复制代码

当发送该信息时,接受方收到后就会明白,这是个 “key/value” 形式的数据,并且"name" 后是姓名,"age" 后是年龄。

那如何压缩该数据呢?

我们可以删除 “花括号”、“name”、“age” 以及其它的 “冒号”、“逗号”、“引号” 等结构数据。

laomiao18
复制代码

那这样删除了,接收方怎么知道,哪个是姓名?哪个是年龄?

删除 ”结构“

只需要发送方和接收方都保留这份数据的 ”结构“ 就行,发送方只发送数据,接收方接收到数据后,根据本地保留的 ”结构“ 去解析数据就 OK。

假设,该 "结构" 如下,这不是真实存在的,只是为了方便给大家描述。

{
  name string 7
  age int 1
}
复制代码

通过该 ”结构“ 就可以知道:

  • name 数据在 age 数据之前。
  • name 数据类型为 string,age 数据类型为 int。
  • name 数据字节长度为 7,age 数据字节长度为 1。

接收方只需要拿着这份 ”结构“ 就知道了 "laomiao18" 数据如何解析。

自描述

但这样还是有这些问题:

  1. name 数据如果超过 7 个字节怎么办?
  2. age 数据超过 1 个字节怎么办?
  3. 结构中的顺序不能调整,太死了,怎么办?

当然,发送方和接收方都更新下自己的 ”结构“ 数据,但这样显然不现实,因为数据你不能保证是固定长度。

对于 age 数据,我们可以定义为 4 个字节或 8 个字节,只要可以应对自己的业务即可。但这样还是有问题,空间浪费?

假如,age 定义为 4 个字节,传输的数据为 18,而对于 18 这个数字,只需要 1 个字节就足以了,而剩下 3 个字节都浪费着。但咱又不能定义为 1 个字节,因为有可能会有大数。

那如何压缩 age 数据呢?

对于 Protobuf,会在数据中加入解决以上问题的信息,即数据自己描述自己,简称 ”自描述“。

总结下 Protobuf 做了哪些?如下:

  • 数据中加入 ”字段“ 顺序的信息。
  • 数据中加入类型信息。
  • 最小化压缩整形数据。

Protobuf

Protobuf 在序列化数据时,将 Protobuf 数据类型总共划分为 6 大类,英文称为 "wire type"。

wire typeproto 类型含义
0int32, int64, uint32, uint64, sint32, sint64, bool, enumVarint
1fixed64, sfixed64, double64-bit
2string, bytes, embedded messages, packed repeated fieldsLength-delimited
3groups (废弃)Start group
4groups (废弃)End group
5fixed32, sfixed32, float32-bit

"wire type" 中的 ”3“ 和 ”4“ 类型已废弃,这块不做讲解。

下来通过一个 message 信息展开说明,如下:

message HelloRequest {
  string name = 1;
  int32 num = 2;
  float height = 3;
  repeated int32 hobbies= 4;
}
复制代码

这就好比我上面所说的 ”结构“,发送方和接收方就是通过该结构去解析数据。现在我们就针对上面留下的问题一一说明。

1. 类型和顺序

那传输的数据中如何保存 ”数据类型“ 和 ”顺序“?

数据类型对应到 "wire type",顺序对应到 ”field number“。假如 int32 num = 2 对应如下:

  • wire type:0,通过上面表格对应。
  • field number:2,字段后的唯一编码。

将这两个信息按照如下公式组装:

(field_number << 3) | wire_type
复制代码

带入得:

(2 << 3) | 0 
→ 16
复制代码

2. Varint

对于 num 字段保存的数据如何如何压缩?假如 num 存储的数据为 300。按照 4 字节存储如下:

00000000 00000000 00000001 00101100
复制代码

从结果可以看到,真实有效的数据只有 2 字节,为了压缩,面对不同的数据大小会占用不用的字节数。

那如何记录数据长度?我们可以再增加一个字节去记录真实数据所占用的实际字节数。对于 300 数据,增加一个字节记录长度,那下来和数据一块总共需要 3 个字节。那还有什么办法再减少字节数吗?

当然会有呀,不然我就说了一堆废话,咱继续。

请出 Varint 算法,过程如下:

  • 将数据以 7 位为一组进行分割;
  • 将组的顺序颠倒,即:将 ”高位 → 低位“ 规则,改为 ”低位 → 高位“;
  • 识别每一组,如果该组后还有数据,就在该组前增加一位 ”1”,否则增加 “0”。

将数据 300 带入该算法,过程如下:

300: 00000000 00000000 00000001 00101100
→ 7 位分割:0000 0000000 0000000 0000010 0101100
→ 颠倒顺序:0101100 0000010 0000000 0000
→ 组前加 1/0:10101100 00000010
→ 十进制:172 2
复制代码

按照这套算法下来,将数据压缩为 2 个字节存储。而接收方拿到字节数据后,只需要按照高位识别,如果为 0,说明之后没有数据了。

最终,对于 int32 num = 2 结构和数据 300,压缩后的结果为:

16 172 2
复制代码

3. Length-delimited

现在说说 string name = 1 ,该类型对应的 "wire type" 为 2,"field number" 为 2。记录 “顺序” 和 “类型” 方式和上面讲的一样。

重点说说数据如何记录,相比 Varint 算法,该类型就简单多了,只需要使用 Varint 算法记录数据的字节长度。

假如,name 的值为 "miao",最终结果为:

10 4 109 105 97 111
复制代码

解释:

  • 10:(2 << 3) | 2
  • 4:字符串长度。
  • 之后:按照 "UTF-8" 编码保存。

对于 message 嵌套、repeated (数组或切片)、字节数组,也是按照该算法得到。

例如,repeated int32 hobbies= 4 ,假设 hobbies 数据为 [10, 20],最终结果为:

34 2 10 20
复制代码

4. 浮点数

针对浮点类型,就更简单了,浮点数据使用固定字节保存,记录 “顺序” 和 “类型” 依然是上面讲的。

假如,float height = 3 ,该类型对应的 "wire type" 为 5,数据假设为 52.1,最终结果为:

29 102 102 80 66
复制代码

解释:

  • 29:(3 << 3) | 5
  • 之后:使用固定字节数 4。

如果使用了双精度,那对应的 "wire type" 为 1,数据占用字节数为 8。

5. sint32/sint64

这两个类型不知道你在写 proto 文件时有没有用到,明白这个很重要,不然有时候数据就不能起被到压缩的作用。

上面讲到的 Varint 算法中,我们知道了以 7 位一组,再增加一位 “识别位” 来起到压缩数据的作用。但存在一个问题,倘若存在负数时,那这种压缩方式就失效了。

至于为啥?如何解决的?

我先说结果,如果写 proto 文件时,设置的数据类型为 sint32 或 sint64 时,将采用 ZigZag 算法进行数据压缩。

ZigZag 算法我就不重复讲解了,直接看上一篇

小结

学完本篇我们知道了 Protobuf 怎么做到了压缩数据。简单说下,就是删除一些没用的信息,采用自描述的方式记录 “类型”、“顺序”、“数据”。

而对于类型,只记录了 "wire type",该类型确定了数据的大概处理方式。

那说它就一定比 JSON、XML 好吗?也不是。

因为要采用 Protobuf 方式传输数据,发送方和接收方必须采用同一套结构规则,也可以说 “协议”。所以,如果想提高数据的阅读性,降低这种规则的配合,就可以使用 JSON、XML。

后面我会使用 Go 语言实现 Protobuf 序列化和反序列化的核心算法,只要这样我才觉得真的明白了该算法的真谛。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值