Protocal Buffers(简称protobuf)是Google的一项技术,用于结构化的数据序列化、反序列化。

Protobuf的使用比较广泛,常用于RPC 系统(Remote Procedure Call Protocol System)和持续数据存储系统。其主要优点是空间开销小和性能比较好,类似于XML生成和解析,但protobuf的效率高于XML,不过protobuf生成的是字节码,可读性比XML差。

相关官方文档:

Protocol Buffers官网:https://developers.google.com/protocol-buffers/

Protocol Buffers官网(中文):https://developers.google.com/protocol-buffers/?hl=zh-CN

Git地址:https://github.com/google/protobuf

Java API:https://developers.google.com/protocol-buffers/docs/reference/java/?hl=zh-CN

proro文件的编写指南:https://developers.google.com/protocol-buffers/docs/style?hl=zh-CN

Java使用指南:https://developers.google.com/protocol-buffers/docs/javatutorial?hl=zh-CN

一.protobuf的基本应用

使用protobuf开发的基本步骤为:

1.配置开发环境 安装protocol 代码编译器

2.编写对应的.proto文件,定义序列化对象结构

3.使用编译器生成对应的序列化工具类

4.编写自己的应用

使用github上的protoc-3.5.1-win32.zip https://github.com/google/protobuf/releases

每个Protobuf 的 字段 都有一定的格式:

限定修饰符 | 数据类型 | 字段名称 | = | 字段编码值 | [字段默认值]

1.限定修饰符: required/optional/repeated

Required:表示是一个必须字段,缺失该字段会引发编解码异常,导致消息被丢弃。

Optional:表示是一个可选字段。

Repeated:表示该字段可重复,每次可以包含0~N个值,表示集合

2.数据类型

protobuf定义了一些基本的数据类型

string/bytes/bool/int32/int64/float/double

enum 枚举类     message 自定义类

3.字段名称

字段名称的命名方式与C、Java等语言的变量命名方式几乎是相同的。

protobuf建议字段的命名采用以下划线分割的驼峰式。例如 建议使用first_name 而不是firstName.

4.字段编码值

编码值的取值范围为 1~2^32(4294967296)。

相同的编码值,其限定修饰符和数据类型必须相同。

消息中的字段的编码值无需连续,只要是合法的,并且不能在同一个消息中有字段包含相同的编码值。不过通常习惯使用连续的字段编码值,比较易于理解

⑤.默认值。

在发送数据时,对于required数据类型,如果用户没有设置值,则使用默认值传递。当接受数据时,对于optional字段,如果没有接收到对应值,则设置为默认值

以下是一个简单的User对象的proto文件

syntax="proto2";option java_package = "com.chenpp.serializer.protobuf";
option java_outer_classname="UserProto";
message User {//1,2表示当前序列化后的字段顺序required int32 age  = 2; //年龄required string name = 1;//姓名}

使用proto自己的编译器进行编译,生成实体类

protoc.exe --java_out=./java   ./user.proto

–java_out 后面是生成java文件存放地址 
最后的参数是proto文件的名称,可以写绝对地址,也可以直接写proto文件名称

引入protobuf对应的dependency,打印对应的序列化结果:

 <dependency><groupId>com.google.protobuf</groupId><artifactId>protobuf-java</artifactId><version>3.5.0</version></dependency>
public class ProtoSerializer implements ISerializer {public <T> byte[] serialize(T obj) {UserProto.User user = UserProto.User.newBuilder().setAge(21).setName("chenpp").build();return user.toByteArray();}public <T> T deserialize(byte[] data, Class<T> clazz) {try {UserProto.User user = UserProto.User.parseFrom(data);return (T) user;} catch (InvalidProtocolBufferException e) {e.printStackTrace();}return null;}
}==================protobuf========================
protobuf {age:21}序列化后的byte[]的length: 10
10  6  99  104  101  110  112  112  16  21
name: "chenpp"
age: 21

二.protobuf序列化的原理

之前那篇文章,讲过Json里的序列化结果为: { "name":"chenpp","age":21}  -- 一共26个字节,而想要将其进行进一步压缩,就需要去掉一些冗余的字节

思路:1)能不能去掉定义属性(约定1=name,2=age)  约定了字段,约定了类型  去除分隔符(引号,冒号,逗号之类的)

2)压缩数字,因为日常经常使用到的都是一些比较小的数字,一共int占4个字节,但实际有效的字节数没有那么多

把英文转化成数字(ASCII),并对数字进行压缩

protobuf里使用到了两种压缩算法:varint和zigzag算法

varint算法

这是针对无符号整数,一种压缩方式

压缩方法:

1.对一个无符号整数,将其换算成二进制

2.从右往左取7位,然后在最高位补1

3.一直继续,直到取到最后一个有意义的字节(在最后一个有意义的字节上最高位补0)

4.先取到的字节排在后取到的字节前面,得到一个新的字节,转换成十进制就是压缩的结果

以:500为例:其实有意义的就是2个字节

0000 0001 1111  0100= 2^2+2^4+2^5+2^6+2^7+2^8 = 4+16+32+64+128+256 = 500

按照其压缩方式得到的新的二进制字节为:

1111 0100  | 0000 0011

1111 0100 代表的是负数,使用补码(正数取反+1),并且最高位符号位为1

转化后: 先 -1为 1111 0011   取反  0000 1100 = 12

计算出来就是 -12 3

字符如何转化成数字编码

对于英文字母,这里的name字段,使用ASCII码对照表查找对应的数字

chenpp: 对应的ASCII码

c-99  h-104  e-101  n-110  p-112

按照varint的算法 取7位补最高位为1(最后一个字节最高位补0)

对于小于127(2^7-1=127)的数字,其有效字节只有1位,压缩的时候最高位补0,故压缩之前和压缩之后的数字没有变化

protobuf的存储格式

protobuf采用T-L-V的格式进行存储

[Tag | length | value ]

l其中length为可选, 但是string必须有length(这样在反序列化的时候程序才知道该字符串从哪里开始到哪里结束), 而int是不需要length的

Tag:字段标识符,用于标识字段 其值等于

field_number(当前字段的编号,第几个字段)<<3|wire_type(int64/int32/可变长度string)

Length:Value的字节长度 (string需要有,int不需要)

Value:消息字段经过编码后的值

Age:int32  2<<3|0 = 16

Name: string 1<<3|2 = 10

在反序列化的时候根据tag mod 8 的余数判断对应的wireType,从而知道该字段对应的存储方式和编码方式

对应User(name="chenpp",age=21)按照varint进行压缩后其序列化结果为:

c-99  h-104  e-101  n-110  p-112

String:tag-length-value

Int32:tag-value

10        6          99      104      101    110   112   112      16         21

Tag – length  -  c  -     h     –   e    -   n   -   p   -  p   -  Tag -  Value

根据上述压缩算法可知:对于int32/int64,value有且只有最后一个字节为正数,故当遇到第一个为正数的字节时就知道其value值已经获取完毕,所以对于int32类型的字段,不需要length,只需要tag和value就足够了

ZigZag算法

在计算机中,负数会被表示为很大的整数,因为负数的符号位在最高位,如果使用varint算法进行压缩的话会需要 32/7 ~ 5个字节,反

而加大了空间的开销.故在protobuf中对于有符号整数会使用sint32/sint64来表示。protobuf中负数的压缩方式是先使用ZigZag算把有符号数(无论数值是正数还是负数,都会进行一次压缩计算)转化为无符号数,再使用varint进行压缩

ZigZag算法的思路:

负数之所以不好压缩:一个原因是因为其最高位为1,另一个原因是对于绝对值比较小的负数,其正数会有很多的前导零,那么在使用补码表示负数的时候(取反+1),会导致负数会有很多的前导1,使得无法压缩

所以ZigZag采用的办法就是:先将符号位从最高位移动到最低位,其余数字均往前移动1位;然后再对所有的数字(符号位除外)进行取反,这样得到的计算结果就是一个可以压缩的数字(符号位不占据最高位,而小绝对值的数值由于取反操作其前导1都变为了前导0)

比方说-300:

其对应的正数的原码为: 0000 0000 0000 0000 0000 0001 0010 1100

取反: 1111 1111 1111 1111 1111 1110 1101 0011

再+1:    1111 1111 1111 1111  1111 1110 1101 0100 (-300)

移动符号位之后: 1111 1111 1111 1111 1111 1101 1010 1001

取反:0000 0000 0000 0000 0000 0010 0101 0111

计算后为0010 0101 0111 = 599

在ZigZag算法里也是使用这种思路对有符号整数进行压缩的,将其转化成表达式就是

Sint32: (n<<1)^ (n>>31)

Sint64: (n<<1)^(n>>63)

当n为正数时,n>>31为0000 0000 0000 0000 0000 0000 0000 0000;当n为负数时,n>>31为1111 1111 1111 1111 1111 1111 1111 1111

(n>>31)与(n<<1)进行异或之后,如果n为正数,(n>>31)^(n<<1) =n<<1;如果n为负数,其计算结果和上述所说的最高位移动到最后,然后取反效果是一样的(n<<1补的最低位为0和n>>31异或运算之后一定为1,而其他位上与1做异或运算相当于取反),这样一来就可以使用varint进行压缩计算了

对-300的ZigZag计算结果:599 使用varint算法进行压缩

得到1101 0111   0000  0100

其结果为:-41   4

到此,protobuf的压缩原理就介绍完了

ProtoBuf的使用以及原理分析相关推荐

  1. java signature 性能_Java常见bean mapper的性能及原理分析

    背景 在分层的代码架构中,层与层之间的对象避免不了要做很多转换.赋值等操作,这些操作重复且繁琐,于是乎催生出很多工具来优雅,高效地完成这个操作,有BeanUtils.BeanCopier.Dozer. ...

  2. Select函数实现原理分析

    转载自 http://blog.chinaunix.net/uid-20643761-id-1594860.html select需要驱动程序的支持,驱动程序实现fops内的poll函数.select ...

  3. spring ioc原理分析

    spring ioc原理分析 spring ioc 的概念 简单工厂方法 spirng ioc实现原理 spring ioc的概念 ioc: 控制反转 将对象的创建由spring管理.比如,我们以前用 ...

  4. 一次 SQL 查询优化原理分析(900W+ 数据,从 17s 到 300ms)

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 来源:Muscleape jianshu.com/p/0768eb ...

  5. 原理分析_变色近视眼镜原理分析

    随着眼镜的发展,眼镜的外型变得越来越好看,并且眼镜的颜色也变得多姿多彩,让佩戴眼镜的你变得越来越时尚.变色近视眼镜就是由此产生的新型眼镜.变色镜可以随着阳光的强弱变换不同的色彩. 变色眼镜的原理分析 ...

  6. jieba分词_从语言模型原理分析如何jieba更细粒度的分词

    jieba分词是作中文分词常用的一种工具,之前也记录过源码及原理学习.但有的时候发现分词的结果并不是自己最想要的.比如分词"重庆邮电大学",使用精确模式+HMM分词结果是[&quo ...

  7. EJB调用原理分析 (飞茂EJB)

    EJB调用原理分析 EJB调用原理分析 作者:robbin (MSN:robbin_fan AT hotmail DOT com) 版权声明:本文严禁转载,如有转载请求,请和作者联系 一个远程对象至少 ...

  8. 深入掌握Java技术 EJB调用原理分析

      深入掌握Java技术 EJB调用原理分析     一个远程对象至少要包括4个class文件:远程对象:远程对象的接口:实现远程接口的对象的stub:对象的skeleton这4个class文件. 在 ...

  9. 神经网络(NN)+反向传播算法(Backpropagation/BP)+交叉熵+softmax原理分析

    神经网络如何利用反向传播算法进行参数更新,加入交叉熵和softmax又会如何变化? 其中的数学原理分析:请点击这里. 转载于:https://www.cnblogs.com/code-wangjun/ ...

最新文章

  1. 一款遥控器拆解之后可利用的元器件
  2. C++17中那些值得关注的特性(上)
  3. RabbitMQ RPC远程调用模式
  4. 记录 之 tensorflow 常用函数:tf.split(),tf.clip_by_value() 和 tf.cond()
  5. Oracle入门(十四.7)之良好的编程习惯
  6. 域对象的引用,ActionContext 和ServletActionContext类的使用
  7. bat批量修改及替换文件内容
  8. JVM垃圾回收机制与算法详解
  9. ipmitool 远程操作BMC控制服务器
  10. win7或win10系统的打印机共享设置步骤
  11. 电脑换硬盘要重装系统吗
  12. navicat 连接 oracle (最全解读)
  13. PAT 1066. Root of AVL Tree (25) 回レ!雪月AVL
  14. win8计算机关机时 重新配置windows 以后打印机不能用了,win8系统打印机发送打印任务后不打印自动消失的技巧介绍...
  15. 微信小程序ocr身份证扫描
  16. 一个后端开发的 Vue 笔记【入门级】
  17. 网络安全学习笔记——红队实战攻防(上)
  18. 如何利用html制作电影影评网,HTML制作电影影评网 - 手册网
  19. AndroidStudio制作“我”的界面,设置,修改密码,设置密保和找回密码
  20. Qt编写安防视频监控系统42-用户权限

热门文章

  1. c语言程序设计了解,C语言程序设计
  2. lamp自动部署工具_salt实现lamp自动化部署
  3. python动态生成数据库表_Python-Flask:动态创建表的示例详解
  4. 洛谷P2426 删数
  5. QGridLayout比例
  6. router3 BGP1 基础部分
  7. 微软一站式示例代码库 8 月新代码示例发布
  8. 计篇-之一文言文翻译
  9. URL(统一资源定位符)
  10. LibreOJ - 3083 与或和(单调栈+位运算)