dubbo kryo序列化_为什么如此高效?解密kryo各个数据类型的序列化编码机制,强...

用过dubbo的开发人员,在替换序列化时都会根据“经验”来选kryo为序列化框架,其原因是序列化协议非常高效,超过java原生序列化协议,hessian2协议,那kryo为什么高效呢?

序列化协议,所谓的高效,通常应该从两方面考虑:

  1. 序列化后的二进制序列大小。
  2. 序列化,反序列化的速率。

本节将重点探讨,kryo在减少序列化化二进制流上做的努力。

序列化:将各种数据类型(基本类型,包装类型,对象,数组,集合)等序列化为字节序列的过程。

反序列化:将字节转换为各种数据类型(基本类型,包装类型,对象,数组,集合)。

java中定义的数据类型所对应的序列化器在Kryo的构造函数中构造,其代码截图:

d0a54a8016f1e449f2cc8efaa706dcb7.png

接下来将详细介绍java常用的数据类型的序列化机制,即Kryo是如何编码二进制流。

1,DefaultSerializers $ IntSerializer

int类型序列化

static public class IntSerializer extends Serializer {
   {setImmutable(true);}public void write (Kryo kryo, Output output, Integer object) {output.writeInt(object, false);}public Integer read (Kryo kryo, Input input, Class type) {return input.readInt(false);}}

1.1整数---> byte [](序列化)

输出#writeInt

public int writeInt (int value, boolean optimizePositive) throws KryoException { // @1return writeVarInt(value, optimizePositive);  // @2}

代码@ 1:布尔值optimizePositive,是否优化绝对值。如果optimizePositive:false,将对值进行移位运算,如果是正数,则存放的替换原值的变量,如果是负数的话,放置的位移绝对值的形式变量一,其算法为:value =(值<< 1)^(值>> 31),在反序列化时,通过该算法恢复原值:((结果>>> 1) ^-(result&1))。

代码@ 2:调用writeVarInt,采用变长编码来存储int而不是固定4字节。

输出#writeVarInt

public int writeVarInt (int value, boolean optimizePositive) throws KryoException {if (!optimizePositive) value = (value << 1) ^ (value >> 31);if (value >>> 7 == 0) {                                           // @1 require(1);                                                    buffer[position++] = (byte)value;                  return 1;}if (value >>> 14 == 0) {                                          // @2require(2);buffer[position++] = (byte)((value & 0x7F) | 0x80);buffer[position++] = (byte)(value >>> 7);return 2;}if (value >>> 21 == 0) {require(3);buffer[position++] = (byte)((value & 0x7F) | 0x80);buffer[position++] = (byte)(value >>> 7 | 0x80);buffer[position++] = (byte)(value >>> 14);return 3;}if (value >>> 28 == 0) {require(4);buffer[position++] = (byte)((value & 0x7F) | 0x80);buffer[position++] = (byte)(value >>> 7 | 0x80);buffer[position++] = (byte)(value >>> 14 | 0x80);buffer[position++] = (byte)(value >>> 21);return 4;}require(5);buffer[position++] = (byte)((value & 0x7F) | 0x80);buffer[position++] = (byte)(value >>> 7 | 0x80);buffer[position++] = (byte)(value >>> 14 | 0x80);buffer[position++] = (byte)(value >>> 21 | 0x80);buffer[position++] = (byte)(value >>> 28);return 5;}

其思想是采用变长字节来存储int类型的数据,int在java是固定4字节,由于在应用中,一般使用的int数据都不会很大,4个字节中,存在高位字节全是存储0的情况,故kryo为了减少在序列化流中的大小,尝试按需分配,kryo采用1-5个字节来存储int数据,为什么int类型在JAVA中最多4个字节,为什么变长int可能需要5个字节才能存储呢?这与变长字节需要标志位有关,从而根据代码来解释kryo关于int序列化字节的编码规则。

代码@ 1:值>>> 7 == 0,一个数字,无符号右移(高位补0)7位后为0,说明该数字只占一个字节,并且高同轴必须为0,也就是该数字的范围在0-127(2 ^ 7 -1),对于字节的高位,低位的说明如下:

308186d07965c07f5059552b5eaa224d.png

如果该值范围为0-127则使用1个字节存储int即可。在操作缓存区时buffer [position ++] =(byte)value,需要向输出的缓存区申请1个字节的空间,然后进行赋值,并返回本次申请的存储空间,对于require方法在Byte [],String序列化时重点讲解,包含缓存区的扩容,输出与输出流结合使用时的相关机制。

代码@ 2:值>>> 14 == 0,如果数字的范围在0到2 ^ 14-1范围之间,则需要两个字节存储,这里为什么是14,其首要是,对于一个字第中的8位,kryo需要将高位用来当标记位置,使用标识是否还需要读取下一个字节。1:表示需要,0:表示不需要,也就是一个数据的结束。在变长int存储过中,一个字节8位kryo可用于存储数字有效位为7位。

表示演示一下:kryo两字节能存储的数据的特点是高字节中前两位为0,例如:0011 1011 0 010 1001其​存储方式为buffer [0] =先存最后字节的低7位,010 1001,然后第一位之前,加1,表示还需要申请第二个字节来存储。此时buffer [0] = 1010 1001 buffer [1] =存储011 1011 0(这个0是原第一个字节未存储的部分),此时缓冲区[1]的8位中的高位为0,表示存储结束。

下图展示了kryo用2个字节存储一个int类型的数据的示意图。

ef33e6cf6bc4a09404123514fc27a087.png

同理,用3个字节可以表示2 ^ 21 -1。kryo使用变长字节(1-5)个字节来存储int类型(java中固定占4字节)。

1.2 int反序列化(byte [] ---> int)

buffer [0] =低位,buffer [1]高位,具体解码实现为:Input#readVarInt,反序列化就是根据上述编码规则,将byte []序列化为int数字。

/** Reads a 1-5 byte int. It is guaranteed that a varible length encoding will be used. */public int readVarInt (boolean optimizePositive) throws KryoException {if (require(1) < 5) return readInt_slow(optimizePositive);int b = buffer[position++];int result = b & 0x7F;if ((b & 0x80) != 0) {byte[] buffer = this.buffer;b = buffer[position++];result |= (b & 0x7F) << 7;if ((b & 0x80) != 0) {b = buffer[position++];result |= (b & 0x7F) << 14;if ((b & 0x80) != 0) {b = buffer[position++];result |= (b & 0x7F) << 21;if ((b & 0x80) != 0) {b = buffer[position++];result |= (b & 0x7F) << 28;}}}}return optimizePositive ? result : ((result >>> 1) ^ -(result & 1));}

Input#require(count)返回的是缓存区剩余字节数(变量)。其实现思路是,一个一个字节的读取,读到第一个字节后,首先提取有效存储位的数据, buffer [0]&0x7F,然后判断高位是否为1,如果不为1,直接返回,如果为1,则继续读取第二位buffer [1],同样首先提取有效数据位(低7位),然后对这数据向左移7位,在与buffer [0]进行或运算。也就是,varint的存放是小端序列,越先读到的位,在整个i

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值