参考官网, 序列化原理

底层二进制存储

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)。

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

  1. HBase底层存储原理

    HBase底层存储原理——我靠,和cassandra本质上没有区别啊!都是kv 列存储,只是一个是p2p另一个是集中式而已! 首先HBase不同于一般的关系数据库, 它是一个适合于非结构化数据存储的数 ...

  2. python-变量&底层存储原理

    目录 1.变量 1.变量如何使用 2.变量存储的原理 --[ 重点 ] 3.变量存储要遵循印射关系 4.变量三要素 2.常量 3.底层优化 4.垃圾回收机制 1.变量 1.变量如何使用 1.什么是变量 ...

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

    理解HBase(一个开源的Google的BigTable实际应用)最大的困难是HBase的数据结构概念究竟是什么?首先HBase不同于一般的关系数据库,它是一个适合于非结构化数据存储的数据库.另一个不 ...

  4. Redis数据的底层存储原理

    redis底层是用什么结构来存储数据的呢? 我们从源码上去理解就会容易的多:   redis底层是使用C语言来编写的,我们可以看到它的数据结构声明.一个 dict 有两个dictht,一个dictht ...

  5. 列式数据库~clickhouse 底层存储原理

    简介:今天介绍列式数据库的一些基本原理 一  数据目录 Data目录 数据存储目录,数据按照part分成多个文件夹,每个文件夹下存储相应数据和对应的元信息文件 Metadata 表定义语句,存储所有表 ...

  6. V7000存储数据恢复_底层结构原理拆解及Mdisk磁盘掉线数据恢复方法

    Storwize V7000(也就是我们常说的V7000)是新推出的一款中端存储系统,这款系统的定位虽然在中端,但是Storwize V7000提供有存储管理功能,这一功能以前只有高端存储才拥有(例如 ...

  7. mongodb底层存储和索引原理——本质是文档数据库,无表设计,同时wiredTiger存储引擎支持文档级别的锁,MMAPv1引擎基于mmap,二级索引(二级是文档的存储位置信息『文件id + 文件内offset 』)

    MongoDB是面向文档的数据库管理系统DBMS(显然mongodb不是oracle那样的RDBMS,而仅仅是DBMS). 想想一下MySQL中没有任何关系型数据库的表,而由JSON类型的对象组成数据 ...

  8. Java并发之底层实现原理学习笔记

    本篇博文将介绍java并发底层的实现原理,我们知道java实现的并发操作最后肯定是由我们的CPU完成的,中间经历了将java源码编译成.class文件,然后进行加载,然后虚拟机执行引擎进行执行,解释为 ...

  9. iOS weak底层实现原理

    今年年底做了很多决定,离开工作三年的深圳,来到了上海,发现深圳和上海在苹果这方面还是差距有点大的,上海的市场8成使用swift编程,而深圳8成的使用OC,这点还是比较让准备来上海打拼的苹果工程师有点小 ...

随机推荐

  1. C#重启IIS

    using System.Diagnostics; using System.ServiceProcess; //ServiceController sc1 = new ServiceControll ...

  2. Android 禁止屏幕旋转 & 旋转屏幕时保持Activity内容

    Android 禁止屏幕旋转 & 旋转屏幕时保持Activity内容   1.在应用中固定屏幕方向.        在AndroidManifest.xml的activity中加入:     ...

  3. http://4526621.blog.51cto.com/4516621/1343369

    http://4526621.blog.51cto.com/4516621/1343369

  4. 如何取得nginx做反向代理时的真实IP?

    1. 编译 对于client -> nginx reverse proxy -> apache, 要想在程序中取得真实的IP,在执行nginx的configure时,必须指定参数" ...

  5. swift NSURLSession 使用方法

    //NSURLSession /*创建一个session ->创建一个task -> 启动task -> 处理回调 */ //http://www.douban.com/j/app/ ...

  6. 如何查看非自己提交的请求的结果 - 深入浅出Oracle EBS之杂项技术荟萃

    如何查看非自己提交的请求的结果定位要找的请求SQL举例:SELECT req.request_id,       fcp.user_concurrent_program_name,       usr ...

  7. vs2008快捷键极其技巧

    vs2008快捷键极其技巧 1. 工具: Microsoft Visual Studio 2008 Version 9.0.21022.8 RTM Microsoft .NET Framework V ...

  8. winform 开发中 把耗时操作 封装起来 异步执行(.net 4.0)

    .先定义一个 BackgroundTask.cs 代码如下: public class BackgroundTask { private static WaitDialogForm LoadingDl ...

  9. ActiveX 控件重绘无效问题,用CClientDC 而不是CPaintDC

    ActiveX 控件重绘子控件时,用Invalid()会出现无效的情况即不会更新界面. OnPaint 方法里,是用的 CPaintDC,经测试无效,后换CClientDC,发现可以,百度查他们的区别 ...

  10. .net core 使用 redis

    .net core 使用 redis 个人感觉.net core 对于微软技术而言有很重要的意义 ,所以最近已有时间就想看一看关于.net core 的文章. 今天我就来写一写如何在.net core ...