ProtoBuf的使用以及原理分析
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的使用以及原理分析相关推荐
- java signature 性能_Java常见bean mapper的性能及原理分析
背景 在分层的代码架构中,层与层之间的对象避免不了要做很多转换.赋值等操作,这些操作重复且繁琐,于是乎催生出很多工具来优雅,高效地完成这个操作,有BeanUtils.BeanCopier.Dozer. ...
- Select函数实现原理分析
转载自 http://blog.chinaunix.net/uid-20643761-id-1594860.html select需要驱动程序的支持,驱动程序实现fops内的poll函数.select ...
- spring ioc原理分析
spring ioc原理分析 spring ioc 的概念 简单工厂方法 spirng ioc实现原理 spring ioc的概念 ioc: 控制反转 将对象的创建由spring管理.比如,我们以前用 ...
- 一次 SQL 查询优化原理分析(900W+ 数据,从 17s 到 300ms)
点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 来源:Muscleape jianshu.com/p/0768eb ...
- 原理分析_变色近视眼镜原理分析
随着眼镜的发展,眼镜的外型变得越来越好看,并且眼镜的颜色也变得多姿多彩,让佩戴眼镜的你变得越来越时尚.变色近视眼镜就是由此产生的新型眼镜.变色镜可以随着阳光的强弱变换不同的色彩. 变色眼镜的原理分析 ...
- jieba分词_从语言模型原理分析如何jieba更细粒度的分词
jieba分词是作中文分词常用的一种工具,之前也记录过源码及原理学习.但有的时候发现分词的结果并不是自己最想要的.比如分词"重庆邮电大学",使用精确模式+HMM分词结果是[&quo ...
- EJB调用原理分析 (飞茂EJB)
EJB调用原理分析 EJB调用原理分析 作者:robbin (MSN:robbin_fan AT hotmail DOT com) 版权声明:本文严禁转载,如有转载请求,请和作者联系 一个远程对象至少 ...
- 深入掌握Java技术 EJB调用原理分析
深入掌握Java技术 EJB调用原理分析 一个远程对象至少要包括4个class文件:远程对象:远程对象的接口:实现远程接口的对象的stub:对象的skeleton这4个class文件. 在 ...
- 神经网络(NN)+反向传播算法(Backpropagation/BP)+交叉熵+softmax原理分析
神经网络如何利用反向传播算法进行参数更新,加入交叉熵和softmax又会如何变化? 其中的数学原理分析:请点击这里. 转载于:https://www.cnblogs.com/code-wangjun/ ...
最新文章
- 一款遥控器拆解之后可利用的元器件
- C++17中那些值得关注的特性(上)
- RabbitMQ RPC远程调用模式
- 记录 之 tensorflow 常用函数:tf.split(),tf.clip_by_value() 和 tf.cond()
- Oracle入门(十四.7)之良好的编程习惯
- 域对象的引用,ActionContext 和ServletActionContext类的使用
- bat批量修改及替换文件内容
- JVM垃圾回收机制与算法详解
- ipmitool 远程操作BMC控制服务器
- win7或win10系统的打印机共享设置步骤
- 电脑换硬盘要重装系统吗
- navicat 连接 oracle (最全解读)
- PAT 1066. Root of AVL Tree (25) 回レ!雪月AVL
- win8计算机关机时 重新配置windows 以后打印机不能用了,win8系统打印机发送打印任务后不打印自动消失的技巧介绍...
- 微信小程序ocr身份证扫描
- 一个后端开发的 Vue 笔记【入门级】
- 网络安全学习笔记——红队实战攻防(上)
- 如何利用html制作电影影评网,HTML制作电影影评网 - 手册网
- AndroidStudio制作“我”的界面,设置,修改密码,设置密保和找回密码
- Qt编写安防视频监控系统42-用户权限