为什么需要自定义序列化协议
上节中proto buff明显比java本身的序列化生成的byte数组短很多,因为java自身的序列化传入了很多信息(比如类信息、类型、字段等),通过自定义序列化协议能够通过自己定义的方式实现序列化和反序列化。
🌰
test1
1 | package com.cn; |
输出
1 | [0,0,0,101,0,0,0,21] |
每次都需要进行位运算,而且先在是转换int,long、float、double转换呢?
test2
1 | package com.cn; |
输出
1 | [0,0,0,101,0,0,0,21] |
简化了很多操作,但是
1 | ByteBuffer buffer = ByteBuffer.allocate(8); |
可是每次需要给定申请的给定空间大小,不能自动扩容
test3(使用netty中的ChannelBuffers)
1 | package com.cn; |
输出
1 | [0,0,0,101,0,0,0,21] |
注意
1 | //channelBuffer根据写指针的位置,获取byte数组大小 |
除了能够自动扩容,还能够自动写入int double等类型,可是还是优缺点,这里没有一个writeString的方法
通过“abc”.getBytes()得到字节数据,可是无法确定字节的大小,不像int知道是四个字节,所以String LIst Map都需要在前面加一个长度字段。
|-|-|
|-|-|
|String List Map | short长度+字节数组|
自定义序列化协议(test4)
SeriaLizer.java (自定义了序列化的规则)
1 | package com.serial; |
注意其中的readString等方法
1 | public String readString() { |
将需要的String(list map一样)的byte大小保存到readbuffer中,反序列化的时候再从里面读出来。
BufferFactory.java
1 | package com.serial; |
Player.java
1 | package com.serial; |
test4.java
1 | package com.cn; |
输出
1 | [0,0,0,0,39,17,0,0。。。。] |
通过Seriazer的封装,结合前面ChannelBuffers的方式,将String list map需要传入大小的问题进行了结局。
对比分析protobuff原理 重点学习proto位运算的原理
初窥
通过上面的例子和上节的protobuff,传入相同的值到Player类中
1 | [0,0,0,0,39,17,0,0。。。。] |
1 | [0,0,0,0,39,17] |
当然上面的数据是我编的,可是protobuff生产出来的byte数组大小,比我们尽最大努力自定义的数组大小还要小很多。
分析protobuff源码
上节中的例子
1 | Player player = builder.build(); |
所以看tobyteArray方法
1 | public byte[] toByteArray() { |
看writeTo方法,writeTo是一个接口方法,查看具体实现类PlayerModule.java
1 | public void writeTo(com.google.protobuf.CodedOutputStream output) |
这里的writeInt64等方法中的第一个参数1234是上节中proto文件中的key,表示是第几个字节
1 | required int64 playerId = 1; |
查看WriteInt32方法
1 | public void writeInt32(final int fieldNumber, final int value) |
查看writeInt32NoTag方法
1 | public void writeInt32NoTag(final int value) throws IOException { |
查看writeRawVarint32方法
1 | public void writeRawVarint32(int value) throws IOException { |
0x7F 转化成二进制 0111 1111
~0x7F 取反后 1000 0000
value& ~0x7F 的结果就是value的1-7位都被置为了0,value是32位,前面还有25位可能有数据,所以可能value & ~0x7F != 0
所以如果value & ~0x7F == 0,说明value值的大小是小于7位的
如果小于后面7位的大小,就写一个字节的数据
1 | public void writeRawByte(final byte value) throws IOException { |
如果大于后面7位的大小
1 | writeRawByte((value & 0x7F) | 0x80); |
value & 0x7F获取1-7位数据
0x80表示成二进制1000 0000
或运算 1xxx xxxx 通过第八位判断后面还有没有数据,如果有数据就是1,如果没有数据就是0,
所以这里说明有数据,往右移7位,接着读,然后在此判断剩下的数据是不是小于七位,如此循环。
因为这里的第一位用来表示还有没有数据,所以只能表示28位的数据位,而真正需要表示的是32位,所以还需要一个字节存储丢失的4位,所以int 的字节长度不是传统的4个,在proto中为伸缩的1-5个字节,long不是传统的8位,在proto中是伸缩的1-9位