Protobuf底层存储原理

参考官网, 序列化原理

底层二进制存储

message Test1 {
optional int32 a = 1;
}

并设置为a=150,序列化到一个文件中,查看文件,得到下面的二进制:
08 96 01
从底层存储的二进制值看出,Protobuf为什么这么快,节省内存了吧。

有以上的结果是因为 varints 这个特殊的东东。它可以让已个int数据类型的存储根据值的大小而自动改变存储的字节数。

varint 中的每个字节,除了最后一个字节,都有最重要的位集——这表示还会有更多的字节。每个字节的低7位用于存储以7位为一组的数字的两个补码表示形式,最先存储的是最低字节。

比如存储数字1,请看二进制格式:
0000 0001
因为只有一个字节,所以最高位是0.

比如存储数字300,请看二进制格式:
1010 1100 0000 0010
计算方法:

  • 1.先删除最高位。因为这位时没意义的,只是告诉我们是否叨叨数字的末尾。
    1010 1100 0000 0010
    → 010 1100 000 0010
  • 2.翻转字节。因为varint最先存储的是最低字节。
    010 1100 000 0010
    →000 0010 010 1100
  • 3.字节相加。还原最终的值。
    → 000 0010 ++ 010 1100
    → 100101100
    → 256 + 32 + 8 + 4 = 300

Protobuf 的快,小就是通过以上来实现的了。。。。。。

消息结构(Message Structure)

Protobuf 是一系列键值对。消息的二进制版本只使用字段的标签作为,每个字段的名称和声明类型只能在解码结束时通过引用消息类型的定义来确定。

当对消息进行编码时,键和值被连接到一个字节流中。当消息被解码时,解析器能够跳过它不认识的字段。通过这种方式,可以使旧代码(相对Protobuf消息定义的新旧)能够兼容新的字段而不用修改代码。为此,行格式消息中每对的“键”实际上是两个值——.proto文件中的字段号+一个线类型,通过该类型可以推断出数据长度。在大多数语言实现中,这个键被称为标记。

数据类型:
| Type | Meaning | Used For |
| --- | --- | --- | --- |
| 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|

流消息中的每个键都是一个varint,其值为(field_number << 3) | wire_type,也就是说,数字的最后三位存储了存储数据包的类型。

例如:
底层存储二进制是:
000 1000

那么原字段的类型就是根据(field_number << 3) | wire_type得到低三位得到 wire( 0 ),是一个 Varint 类型,也就是数字。剩下的几位右移,得到的是1,因此字段标签是1

所以字段原型应该是:
struct Message {
int32 | int64 | uint32 | uint64 | sint32 | sint64 | bool | enum xxx = 1;
};

再来看下两个字节的150:

  • 1.十六进制值为:
    96 01
  • 2.转换为二进制格式:
    1001 0110 0000 0001
  • 3.丢弃最后一位(没意义,只是判断是否是最后一个字节),并且翻转字节(varint是先存储最低字节),最后穿起来得到真正的二进制值,进而得到原值。
    • → 000 0001 ++ 001 0110 (drop the msb and reverse the groups of 7 bits)
      → 10010110
      → 128 + 16 + 4 + 2 = 150

由此看见,多字节的二进制存储,就是多了丢弃最后一位翻转字节的步骤。

更多数据类型

有符号整型(Signed Integers)

在Protobuf中,有符号的编码是利用了ZigZag编码,把有符号类型编码成一个比较大的无符号整型,提高了存储空间和提高序列化速度。

ZigZag编码是一种应用于大量使用小整型的场景的编码算法,可以提高编码速度。

非varint数字(Non-varint Numbers)

类型1,类型5的数字是按照小端序列来存储的。

Strings

类型为2(以长度分隔)意味着该值是varint编码的长度,后跟指定的数据字节数。
看这个例子:

message Test2 {
  optional string b = 2;
}

在应用程序里设置 b 值为testing,序列化后得到下面的二进制串:
12 07 74 65 73 74 69 6e 67

加粗的字节是“testing”的UTF8。这里的键是0x12→字段号= 2,类型= 2。值中的varint长度是7,你看,我们在它后面找到了7个字节——我们的字符串。

嵌入类型(Embedded Messages)

message Test1 {
  optional int32 a = 1;
}
message Test3 {
  optional Test1 c = 3;
}

设置Test1的 a 为150,得到序列化十六进制值:
1a 03 08 96 01

最后三个字节与上面的第一个示例单独Test1并赋值150 (08 96 01)完全相同,它们的前面是数字3,嵌入式消息的处理方式与字符串完全相同(wire type = 2)。

原文地址:https://www.cnblogs.com/liangjf/p/10642230.html

时间: 2024-07-31 21:42:34

Protobuf底层存储原理的相关文章

HBase底层存储原理——我靠,和cassandra本质上没有区别啊!都是kv 列存储,只是一个是p2p另一个是集中式而已!

理解HBase(一个开源的Google的BigTable实际应用)最大的困难是HBase的数据结构概念究竟是什么?首先HBase不同于一般的关系数据库,它是一个适合于非结构化数据存储的数据库.另一个不同的是HBase基于列的而不是基于行的模式. Google's BigTable论文 清楚地解释了什么是BigTable: Bigtable是一个疏松的分布式的持久的多维排序的map,这个map被行键,列键,和时间戳索引.每一个值都是连续的byte数组.(A Bigtable is a sparse

VMware虚拟机底层存储结构和还原快照恢复原理

虚拟机技术是虚拟化技术的一种,所谓虚拟化技术就是将事物从一种形式转变成另一种形式,最常用的虚拟化技术有操作系统中内存的虚拟化,实际运行时用户需要的内存空间可能远远大于物理机器的内存大小,利用内存的虚拟化技术,用户可以将一部分硬盘虚拟化为内存,而这对用户是透明的.又如,可以利用虚拟专用网技术在公共网络中虚拟化一条安全,稳定的"隧道",用户感觉像是使用私有网络一样.虚拟化技术看起来是一种更加安全的数据存储方式,但是世界上并没有100%安全的存储模式,一旦承载虚拟机的底层服务器或者存储出现故

由PHP底层工作原理说起

之前做过.net,java开发,也写过几个Php的网站,似乎3种主要编程语言都接触了.但是越来越觉得自己对编程的整个流程缺乏一个整体的认识,尤其是底层的机制.譬如网络编程,编译原理,服务器端,数据库存储引擎原理等.于是看了一些书,比较经典的有apue,unp,tcp/ip,nginx,mysql的innodb存储引擎,深入理解jvm.渐渐发现无论用什么语言做开发,背后都有linux,shell,c/c++,nginx服务器,mysql的身影.也许只有掌握了这些核心的原理知识,一个程序员才具有核心

Exchange Server 运维管理02:邮箱数据库存储原理

重申一下,出此系列文章的目的是为了加强运维管理的能力,也就是说不是部署或者是常规配置,这就需要掌握一些基本的理论知识.如果有朋友需要了解Exchange的部署或者是基本操作,可以参考其他的资源,也可以看我之前的Exchange系列文章. 本文将了解一下Exchage 2010数据库文件的存储原理,可能Exchange部署配置完成后,客户很少去关心底层数据库文件的存储格式,只要DAG副本能正常复制,用户邮箱正常使用就可以了,当然,这是理想状态,但万一数据库发生故障需要对数据库进行修复或者是还原时候

Java并发编程系列-(8) JMM和底层实现原理

8. JMM和底层实现原理 8.1 线程间的通信与同步 线程之间的通信 线程的通信是指线程之间以何种机制来交换信息.在编程中,线程之间的通信机制有两种,共享内存和消息传递. 在共享内存的并发模型里,线程之间共享程序的公共状态,线程之间通过写-读内存中的公共状态来隐式进行通信,典型的共享内存通信方式就是通过共享对象进行通信. 在消息传递的并发模型里,线程之间没有公共状态,线程之间必须通过明确的发送消息来显式进行通信,在java中典型的消息传递方式就是wait()和notify(). 线程之间的同步

迭代器Iterator的底层实现原理

第一步:没有接口的迭代器简单实现原理 1 package com.bjsxt.xiaofei; 2 /** 3 * 迭代器底层原理 4 * 方法: 5 * hasNext() 6 * next() 7 * remove() 8 * @ClassName: MyAarryList 9 * @Description: TODO(这里用一句话描述这个类的作用) 10 * @author 尚晓飞 11 * @date 2014-7-29 下午7:06:09 12 * 13 */ 14 public cl

浅议事件异步处理底层实现原理

//主类 package cn.com.likeshow.bluetoothchat; import android.os.Bundle; import android.app.Activity; import android.view.Menu; import android.widget.LinearLayout; import android.widget.TextView; public class MainActivity extends Activity { @Override pr

flash存储原理

norflash 带有 SRAM接口,有足够的地址引脚来寻址,可以很容易地存取其内容每一字节:nandflash器件使用复杂的IO口串行的存取数据,读写操作采用512字节的块(也就是读/写某个字节,必须从其所在的块起始位置开始读/写),这一点有点像硬盘管理类操作,很自然地,基于nandlflash的存储器就可以取代硬盘或其它块设备. 一.半导体存储设备的原理 目前市面上出现了大量的便携式存储设备,这些设备大部分是以半导体芯片为存储介质的.采用半导体存储介质,可以把体积变的很小,便于携带:与硬盘之

Docker底层存储结构

Docker底层存储结构 由于aufs并未并入内核,故而目前只有Ubuntu系统上能够使用aufs作为docker的存储引擎,而其他系统上使用lvm thin provisioning(overlayfs是一个和aufs类似的union filesystem,未来有可能进入内核,但目前还没有:Lvm snapshot are useful for doing e.g. backup of a snapshot, but regress badly in performance when you