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

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

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

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

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

dubbo 3 Jackson 序列化 dubbo序列化方式_kryo序列化协议

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

   1、DefaultSerializers$IntSerializer int类型序列化

static public class IntSerializer extends Serializer<Integer> {
	{
		setImmutable(true);
	}

	public void write (Kryo kryo, Output output, Integer object) {
		output.writeInt(object, false);
	}

	public Integer read (Kryo kryo, Input input, Class<Integer> type) {
		return input.readInt(false);
	}
}

   1.1 Integer —> byte[] (序列化)

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

   代码@1:boolean optimizePositive,是否优化绝对值。如果optimizePositive:false,则会对value进行移位运算,如果是正数,则存放的值为原值的两倍,如果是负数的话,存放的值为绝对值的两倍减去一,其算法为:value = (value << 1) ^ (value >> 31),在反序列化时,通过该算法恢复原值:((result >>> 1) ^ -(result & 1))。
   代码@2:调用writeVarInt,采用变长编码来存储int而不是固定4字节。
   Output#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) {                                          // @2
			require(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序列化byte数组的编码规则。

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

dubbo 3 Jackson 序列化 dubbo序列化方式_kryo序列化协议_02

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

   代码@2:value >>> 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是原第一个字节未存储的部分) ,此时buffer[1]的8位中的高位为0,表示存储结束。

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

dubbo 3 Jackson 序列化 dubbo序列化方式_dubbo_03

   同理,用3个字节可以表示2^21 -1。

   kryo使用变长字节(1-5)个字节来存储int类型(java中固定占4字节)。

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

/** 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的存放是小端序列,越先读到的位,在整个int序列中越靠近低位。
   2、String序列化,其实现类DefaultSerializers$StringSerializer。

static public class StringSerializer extends Serializer<String> {
		{
			setImmutable(true);
			setAcceptsNull(true);      // @1
		}

		public void write (Kryo kryo, Output output, String object) {
			output.writeString(object);
		}

		public String read (Kryo kryo, Input input, Class<String> type) {
			return input.readString();
		}
	}

   代码@1:String位不可变、允许为空,也就是序列化时需要考虑String s = null的情况。
   2.1 序列化 (String ----> byte[])
   Output#writeString

public void writeString (String value) throws KryoException {
		if (value == null) {                                                                                // @1
			writeByte(0x80); // 0 means null, bit 8 means UTF8.
			return;
		}
		int charCount = value.length();      
		if (charCount == 0) {    // @2
			writeByte(1 | 0x80); // 1 means empty string, bit 8 means UTF8.
			return;
		}
		// Detect ASCII.
		boolean ascii = false;
		if (charCount > 1 && charCount < 64) {  // @3
			ascii = true;
			for (int i = 0; i < charCount; i++) {
				int c = value.charAt(i);
				if (c > 127) {
					ascii = false;
					break;
				}
			}
		}
		if (ascii) {     // @4
			if (capacity - position < charCount)
				writeAscii_slow(value, charCount);
			else {
				value.getBytes(0, charCount, buffer, position);
				position += charCount;
			}
			buffer[position - 1] |= 0x80;
		} else {
			writeUtf8Length(charCount + 1);      // @5
			int charIndex = 0;
			if (capacity - position >= charCount) {     // @6
				// Try to write 8 bit chars.
				byte[] buffer = this.buffer;
				int position = this.position;
				for (; charIndex < charCount; charIndex++) {
					int c = value.charAt(charIndex);
					if (c > 127) break;
					buffer[position++] = (byte)c;
				}
				this.position = position;
			}
			if (charIndex < charCount) writeString_slow(value, charCount, charIndex);    // @7
		}
	}

   首先对字符串编码成字节序列,通常采用的编码方式为length:具体内容,通常的做法,表示字符串序列长度为固定字节,例如4位,那kryo是如何来表示的呢?请看下文分析。
   代码@1:如果字符串为null,采用一个字节来表示长度,长度为0,并且该字节的高位填充1,表示字符串使用UTF-8编码,null字符串的最终表示为:1000 0000。
   代码@2:空字符串表示,长度用1来表示,同样高位使用1填充表示字符串使用UTF-8编码,空字符串最终表示为:1000 0001。注:长度为1表示空字符串。
   代码@3:如果字符长度大于1并且小于64,依次检查字符,如果其ascii小于127,则认为可以用ascii来表示单个字符,不能超过127的原因是,其中字节的高一位需要表示编码,0表示ascii,当用ascii编码来表示字符串是,第高2位需要用来表示是否结束标记。
   代码@4:如果使用ascii编码,则单个字符,使用一个字节表示,高1位表示编码标记为,高2位表示是否结束标记。
代码@5:按照UTF-8编码,写入其长度,用变长int(varint)写入字符串长度,具体实现如下:
   Output#writeUtf8Length

private void writeUtf8Length (int value) {
		if (value >>> 6 == 0) {
			require(1);
			buffer[position++] = (byte)(value | 0x80); // Set bit 8.
		} else if (value >>> 13 == 0) {
			require(2);
			byte[] buffer = this.buffer;
			buffer[position++] = (byte)(value | 0x40 | 0x80); // Set bit 7 and 8.
			buffer[position++] = (byte)(value >>> 6);
		} else if (value >>> 20 == 0) {
			require(3);
			byte[] buffer = this.buffer;
			buffer[position++] = (byte)(value | 0x40 | 0x80); // Set bit 7 and 8.
			buffer[position++] = (byte)((value >>> 6) | 0x80); // Set bit 8.
			buffer[position++] = (byte)(value >>> 13);
		} else if (value >>> 27 == 0) {
			require(4);
			byte[] buffer = this.buffer;
			buffer[position++] = (byte)(value | 0x40 | 0x80); // Set bit 7 and 8.
			buffer[position++] = (byte)((value >>> 6) | 0x80); // Set bit 8.
			buffer[position++] = (byte)((value >>> 13) | 0x80); // Set bit 8.
			buffer[position++] = (byte)(value >>> 20);
		} else {
			require(5);
			byte[] buffer = this.buffer;
			buffer[position++] = (byte)(value | 0x40 | 0x80); // Set bit 7 and 8.
			buffer[position++] = (byte)((value >>> 6) | 0x80); // Set bit 8.
			buffer[position++] = (byte)((value >>> 13) | 0x80); // Set bit 8.
			buffer[position++] = (byte)((value >>> 20) | 0x80); // Set bit 8.
			buffer[position++] = (byte)(value >>> 27);
		}
	}

   用来表示字符串长度的编码规则(int),第8位(高位)表示字符串的编码,第7位(高位)表示是否还需要读取下一个字节,也就是结束标记,1表示未结束,0表示结束。一个字节共8位,只有低6位用来存放数据, varint采取的是小端序列。
   代码@6:如果当前缓存区有足够的空间,先尝试将字符串中单字节数据写入到buffer中,碰到第一个非单字节字符时,结束。
   代码@7:将剩余空间写入缓存区,其实现方法:Output#writeString_slow(value, charCount, charIndex)
   Output#writeString_slow

private void writeString_slow (CharSequence value, int charCount, int charIndex) {
		for (; charIndex < charCount; charIndex++) {                                                                            // @1
			if (position == capacity) require(Math.min(, charCount - charIndex));                               // @2
			int c = value.charAt(charIndex);                                                                                        // @3
			if (c <= 0x007F) {                                                                                                               // @4
				buffer[position++] = (byte)c;
			} else if (c > 0x07FF) {                                                                                                       // @5
				buffer[position++] = (byte)(0xE0 | c >> 12 & 0x0F);
				require(2);
				buffer[position++] = (byte)(0x80 | c >> 6 & 0x3F);
				buffer[position++] = (byte)(0x80 | c & 0x3F);
			} else {                                                                                                                                // @6
				buffer[position++] = (byte)(0xC0 | c >> 6 & 0x1F);
				require(1);
				buffer[position++] = (byte)(0x80 | c & 0x3F);
			}
		}
	}

   代码@1:循环遍历字符的字符。
   代码@2:如果当前缓存区已经写满,尝试申请(capacity 与 charCount - charIndex )的最小值,这里无需担心字符不是单字节申请charCount - charIndex空间不足的问题,后面我们会详细分析require方法,字节不够时会触发缓存区扩容或刷写到流中,再重复利用缓存区。
   代码@3:int c = value.charAt(charIndex); 将字符类型转换为int类型,一个中文字符对应一个 int数字,这是因为java使用unicode编码,每个字符占用2个字节,char向int类型转换,就是将2字节的字节编码,转换成对应的二进制,然后用10进制表示的数字。
   代码@4:如果值小于等0x7F(127),直接存储在1个字节中,此时高位4个字节的范围在(0-7)
   代码@5:如果值大于0x07FF(二进制 0000 0111 1111 1111),第一个大于0x7F的值为(0000 1000 0000 0000),即2^12,数据有效位至少12位,使用3字节来存储,具体存储方式为:
   1)buffer[0] :buffer[position++] = (byte)(0xE0 | c >> 12 & 0x0F); 首先将c右移12位再与0x0F进行与操作,其意义就是先提取c的第16-13(4位的值),并与0xE0取或,最终的值为 0xE (16-13)位的值,从Input读取字符串可以看出,是根据0xE0作为存储该字符需要3个字节的依据,并且只取16-13位的值作为其高位的有效位,也就是说字符编码的值,不会超过0XFFFF,也就是两个字节(正好与java unicode编码吻合)。
   2)buffer[1]:存储第12-7(共6位),c >> 6 & 0x3F,然后与0X80进行或,高位设置为1,表示UTF-8编码,其实再反序列化时,这个高位设置为1,未有实际作用。
   3)buffer[2]:存储第6-1(共6位),0x80 | c & 0x3F,同样高位置1。

   2.2 字符串反序列化 (byte[] ----> String)
   在讲解反序列化时,总结一下String序列化的编码规则

   其反序列化的入口为Input#readString,就是按照上述规则进行解析即可,就不深入探讨了,有兴趣的话,可以自己去指定地方查阅。
   3、boolean类型序列化:DefaultSerializers$BooleanSerializer
   序列化:使用1个字节存储boolean类型,如果为true,则写入1,否则写入0。

   4、byte类型序列化:DefaultSerializers$ByteSerializer
序列化:直接将byte写入字节流中即可。

   5、char类型序列化:DefaultSerializers$CharSerializer
   Output#writeChar

/** Writes a 2 byte char. Uses BIG_ENDIAN byte order. */
	public void writeChar (char value) throws KryoException {
		require(2);
		buffer[position++] = (byte)(value >>> 8);
		buffer[position++] = (byte)value;
	}

   6、short类型序列化:DefaultSerializers$ShortSerializer
   Output#writeShort

/** Writes a 2 byte short. Uses BIG_ENDIAN byte order. */
	public void writeShort (int value) throws KryoException {
		require(2);
		buffer[position++] = (byte)(value >>> 8);
		buffer[position++] = (byte)value;
	}

   序列化:与char类型序列化一样,采用大端字节顺序存储。
   7、long类型序列化:DefaultSerializers$LongSerializer
   Output#writeLong

public int writeLong (long value, boolean optimizePositive) throws KryoException {
		return writeVarLong(value, optimizePositive);
}

   8、float类型序列化:DefaultSerializers$FloatSerializer

/** Writes a 4 byte float. */
public void writeFloat (float value) throws KryoException {
	writeInt(Float.floatToIntBits(value));
}
/** Writes a 4 byte int. Uses BIG_ENDIAN byte order. */
public void writeInt (int value) throws KryoException {
	require(4);
	byte[] buffer = this.buffer;
	buffer[position++] = (byte)(value >> 24);
	buffer[position++] = (byte)(value >> 16);
	buffer[position++] = (byte)(value >> 8);
	buffer[position++] = (byte)value;
}

 9、DefaultSerializers$DoubleSerializer
   Output#writeDouble序列化:首先将Double按照IEEE 754编码标准转换为Long,然后才去固定8字节存储。
到目前为止,介绍了8种基本类型(boolean、byte、char、short、int、float、long、double)和String类型的序列化与反序列化。

   10、BigInteger序列化:DefaultSerializers$BigIntegerSerializer

/** Writes an 8 byte double. */
public void writeDouble (double value) throws KryoException {
	writeLong(Double.doubleToLongBits(value));
}
/** Writes an 8 byte long. Uses BIG_ENDIAN byte order. */
public void writeLong (long value) throws KryoException {
	require(8);
	byte[] buffer = this.buffer;
	buffer[position++] = (byte)(value >>> 56);
	buffer[position++] = (byte)(value >>> 48);
	buffer[position++] = (byte)(value >>> 40);
	buffer[position++] = (byte)(value >>> 32);
	buffer[position++] = (byte)(value >>> 24);
	buffer[position++] = (byte)(value >>> 16);
	buffer[position++] = (byte)(value >>> 8);
	buffer[position++] = (byte)value;
}

   11、BigDecimal序列化:DefaultSerializers$BigDecimalSerializer
   BigDecimal的序列化与BigInteger一样,首先是通过BigDecimal#unscaledValue方法返回对应的BigInteger,然后序列化,在反序列化时通过BigInteger创建对应的BigDecimal即可。

KaTeX parse error: Expected 'EOF', got '&' at position 17: …lassSerializer &̲ensp; &ensp;Def…ClassSerializer#write

public void write (Kryo kryo, Output output, Class object) {
	kryo.writeClass(output, object); // @1
	output.writeByte((object != null && object.isPrimitive()) ? 1 : 0); // @2
}

   代码@1:调用Kryo的writeClass方法序列化Class实例。
   代码@2:写入是否是包装类型(针对8种基本类型)。
   接下来我们重点分析Kryo#writeClass

public Registration writeClass (Output output, Class type) {
   if (output == null) throw new IllegalArgumentException("output cannot be null.");
   try {
       return classResolver.writeClass(output, type);    // @1
   } finally {
      if (depth == 0 && autoReset) reset();    // @2
   }
}

   代码@1:首先调用ClassResolver.wreteClass方法。
   代码@2:完成一次写入后,需要重置Kryo中的临时数据结构,这也就是kryo实例非线程安全的原因,其中几个重要的数据结构会再ClassResolver.writeClass中详细说明。
   DefaultClassResolver#writeClass

public Registration writeClass (Output output, Class type) {
    if (type == null) {   // @1
	if (TRACE || (DEBUG && kryo.getDepth() == 1))
            log("Write", null);
	output.writeVarInt(Kryo.NULL, true);
  return null;
    }
    Registration registration = kryo.getRegistration(type);     // @2
    if (registration.getId() == NAME)                                      // @3
writeName(output, type, registration);
    else {
		if (TRACE) 
trace("kryo", "Write class " + registration.getId() + ": " + className(type));
		output.writeVarInt(registration.getId() + 2, true);    // @4
   }
   return registration;
}

   代码@1:如果type为null,则存储Kryo.NULL(0),使用变长int来存储,0在变长int中占用1个字节。
   代码@2:根据type从kryo获取类注册信息,如果有调用kryo#public Registration register (Class type)方法,则会返回其注册关系。
   代码@3:如果不存在注册关系,则需要将类型的全名写入。
   代码@4:如果存在注册关系,则registration.getId()将不等于Kryo.NAME(-1),则将(registration.getId() + 2)使用变长int写入字节流即可。
   从这里看出,如果将类预先注册到kryo中,序列化字节流将变的更小,所谓的kryo类注册机制就是将字符串的类全路径名替换为数字,但数字的分配与注册顺序相关,所有,如果要使用类注册机制,必须在kryo对象创建时首先注册,确保注册顺序一致。
   解析来重点分析一下writeName方法
   DefaultClassResolver#writeName

protected void writeName (Output output, Class type, Registration registration) {
	output.writeVarInt(NAME + 2, true);      // @1
	if (classToNameId != null) {      // @2
		int nameId = classToNameId.get(type, -1);    /
		if (nameId != -1) {   //
			if (TRACE) trace("kryo", "Write class name reference " + nameId + ": " + className(type));
			output.writeVarInt(nameId, true);
			return;
		}
	}
// Only write the class name the first time encountered in object graph.
	if (TRACE) trace("kryo", "Write class name: " + className(type));
	int nameId = nextNameId++;    // @3
	if (classToNameId == null) classToNameId = new IdentityObjectIntMap();    // @4
	classToNameId.put(type, nameId);     // @5
	output.writeVarInt(nameId, true);        // @6
	output.writeString(type.getName());   // @7
}

   代码@1:由于是要写入类的全路径名,故首先使用变长int编码写入一个标记,表示是存储的类名,而不是一个ID。其标志位为 NAME+2 = 1。,存储0表示null。
   代码@2:如果classToNameId 不为空(IdentityObjectIntMap< Class>),根据type获取nameId,如果不为空并且从缓存中能获取到nameId,则直接写入nameId,而不是写入类名,这里指在一次序列化过程中,同一个类名例如(cn.uce.test.Test)只写入一次,其他级联(重复)出现时,为其分配一个ID,进行缓存,具体可以从下面的代码中得知其意图。
   代码@3:首先分配一全局递增的nameId。
   代码@4:如果classToNameId 为空,则创建一个实例。
   代码@5:将type与nameId进行缓存。
   代码@6:写入nameId。
   代码@7:写入type的全路径名。
   注意Kryo#writeClass ,一次序列化Class实例后会调用reset方法,最终会清除本次classToNameId ,classToNameId并不能做一个全据的缓存的主要原因是,在不同的JVM虚拟机中,同一个class type对应的nameId不一定相同,故无法实现共存,只能是作为一个优化,在一次类序列化中,如果存在同一个类型,则第一个写入类全路径名,后面出现的则使用id(int)来存储,节省空间。
   为了加深上述理解,我们再来看一下Class实例的反序列化:
   DefaultClassResolver#readClass

public Registration readClass (Input input) {
	int classID = input.readVarInt(true);    // @1
	switch (classID) {
	case Kryo.NULL:                                 // @2
		if (TRACE || (DEBUG && kryo.getDepth() == 1)) log("Read", null);
		return null;
	case NAME + 2: // Offset for NAME and NULL.      // @3
		return readName(input);
	}
	if (classID == memoizedClassId) return memoizedClassIdValue;
	Registration registration = idToRegistration.get(classID - 2);  
	if (registration == null) throw new KryoException("Encountered unregistered class ID: " + (classID - 2));
  if (TRACE) trace("kryo", "Read class " + (classID - 2) + ": " + className(registration.getType()));
	memoizedClassId = classID;
	memoizedClassIdValue = registration;
	return registration;
}

   代码@1:首先读取一个变长int。
   代码@2:如果为Kryo.NULL表示为null,直接返回null即可。
   代码@3:如果为NAME + 2则表示为存储的是类的全路径名,则调用readName解析类的名字。
   代码@4:如果不为上述值,说明存储的是类型对应的ID值,也就是使用了类注册机制。 之所以idToRegistration.get(classID - 2),是因为在存储时就是nameId + 2。因为,0(代表null),1:代表按类全路径名存储,nameId是从3开始存储。
   接下来再重点看一下readName的实现:
   DefaultClassResolver#readName

protected Registration readName (Input input) {
	int nameId = input.readVarInt(true);   
	if (nameIdToClass == null) nameIdToClass = new IntMap();
	Class type = nameIdToClass.get(nameId);    
	if (type == null) {
		// Only read the class name the first time encountered in object graph.
		String className = input.readString();
		type = getTypeByName(className);
		if (type == null) {
			try {
				type = Class.forName(className, false, kryo.getClassLoader());
			} catch (ClassNotFoundException ex) {
				if (WARN) warn("kryo", "Unable to load class " + className + " with kryo's ClassLoader. Retrying with current..");
				try {
					type = Class.forName(className);
				} catch (ClassNotFoundException e) {
					throw new KryoException("Unable to find class: " + className, ex);
				}
			}
			if (nameToClass == null) nameToClass = new ObjectMap();
			nameToClass.put(className, type);
		   }
		  nameIdToClass.put(nameId, type);
		 if (TRACE) trace("kryo", "Read class name: " + className);
	} else {
		if (TRACE) trace("kryo", "Read class name reference " + nameId + ": " + className(type));
	}
	return kryo.getRegistration(type);
}

首   先读取类的id,因为在序列化类时,如果序列化字符串时,首先先用变长int存储类型的nameId,然后再序列化类的全路径名,这样在一次反序列化时,第一次序列化时,将全列的全路径使用Class.forName实例化对象后,然后存储在局部方法缓存中(IntMap)中,在这一次序列化时再碰到同类型时,则根据id则可以找到对象。

   13、DefaultSerializers$DateSerializer
   java.Util.Date、java.sql.Date等序列化时,只需序列化Date#getTime()返回的long类型,反序列化时根据long类型创建对应的实例即可。long类型的编码使用变长long格式进行序列化。

   14、枚举类型Enum序列化DefaultSerializers$EnumSerializer

static public class EnumSerializer extends Serializer<Enum> {
		{
			setImmutable(true);
			setAcceptsNull(true);
		}

		private Object[] enumConstants;

		public EnumSerializer (Class<? extends Enum> type) {
			enumConstants = type.getEnumConstants();
			if (enumConstants == null) throw new IllegalArgumentException("The type must be an enum: " + type);
		}

		public void write (Kryo kryo, Output output, Enum object) {
			if (object == null) {
				output.writeVarInt(NULL, true);
				return;
			}
			output.writeVarInt(object.ordinal() + 1, true);
		}

		public Enum read (Kryo kryo, Input input, Class<Enum> type) {
			int ordinal = input.readVarInt(true);
			if (ordinal == NULL) return null;
			ordinal--;
			if (ordinal < 0 || ordinal > enumConstants.length - 1)
				throw new KryoException("Invalid ordinal for enum \"" + type.getName() + "\": " + ordinal);
			Object constant = enumConstants[ordinal];
			return (Enum)constant;
		}
	}

   15、EnumSet 类型序列化:DefaultSerializers$EnumSetSerializer

static public class EnumSetSerializer extends Serializer<EnumSet> {
	public void write (Kryo kryo, Output output, EnumSet object) {
		Serializer serializer;
		if (object.isEmpty()) {     // @1
			EnumSet tmp = EnumSet.complementOf(object);    // @2
			if (tmp.isEmpty()) throw new KryoException("An EnumSet must have a defined Enum to be serialized.");
			serializer = kryo.writeClass(output, tmp.iterator().next().getClass()).getSerializer();    // @3
		} else {
			serializer = kryo.writeClass(output, object.iterator().next().getClass()).getSerializer();
		}
		output.writeInt(object.size(), true);  // @4
		for (Object element : object)     // @5
			serializer.write(kryo, output, element);
	  }

	public EnumSet read (Kryo kryo, Input input, Class<EnumSet> type) {
		Registration registration = kryo.readClass(input);
		EnumSet object = EnumSet.noneOf(registration.getType());
		Serializer serializer = registration.getSerializer();
		int length = input.readInt(true);
		for (int i = 0; i < length; i++)
			object.add(serializer.read(kryo, input, null));
		return object;
	}

	public EnumSet copy (Kryo kryo, EnumSet original) {
		return EnumSet.copyOf(original);
	}
}

   EnumSet 是一个专为枚举设计的集合类,EnumSet中的所有元素都必须是指定枚举类型的枚举值。在序列化EnumSet时,需要将EnumSet中存储的枚举类型进行序列化,然后再序列每一个枚举值。
   序列化过程:

   16、StringBuffer序列化DefaultSerializers$StringBufferSerializer
   序列化:与String序列化一致。

   17、StringBuilder序列化DefaultSerializers$StringBuilderSerializer
   序列化:与String序列化一致。

   18、TreeMap序列化DefaultSerializers$TreeMapSerializer

static public class TreeMapSerializer extends MapSerializer {
	public void write (Kryo kryo, Output output, Map map) {
		TreeMap treeMap = (TreeMap)map;
		kryo.writeClassAndObject(output, treeMap.comparator());
		super.write(kryo, output, map);
	}
          //  ...省略部分代码
}

   TreeMap的序列,首先,先序列化TreeMap的比较器,然后再序列化TreeMap中的数据。
序列化数据请看MapSerializer
   MapSerializer#write

public void write (Kryo kryo, Output output, Map map) {
		int length = map.size();
		output.writeInt(length, true);

		Serializer keySerializer = this.keySerializer;
		if (keyGenericType != null) {
			if (keySerializer == null) keySerializer = kryo.getSerializer(keyGenericType);
			keyGenericType = null;
		}
		Serializer valueSerializer = this.valueSerializer;
		if (valueGenericType != null) {
			if (valueSerializer == null) valueSerializer = kryo.getSerializer(valueGenericType);
			valueGenericType = null;
		}

		for (Iterator iter = map.entrySet().iterator(); iter.hasNext();) {
			Entry entry = (Entry)iter.next();
			if (keySerializer != null) {
				if (keysCanBeNull)
					kryo.writeObjectOrNull(output, entry.getKey(), keySerializer);
				else
					kryo.writeObject(output, entry.getKey(), keySerializer);
			} else
				kryo.writeClassAndObject(output, entry.getKey());
			if (valueSerializer != null) {
				if (valuesCanBeNull)
					kryo.writeObjectOrNull(output, entry.getValue(), valueSerializer);
				else
					kryo.writeObject(output, entry.getValue(), valueSerializer);
			} else
				kryo.writeClassAndObject(output, entry.getValue());
		}
	}

   其序列化方法就是遍历Map中的元素,调用Kryo#writeClassAndObject进行序列化,Kryo#writeClassAndObject涉及到Kryo整个序列化流程,将在下节介绍。

1、Kryo序列化的“对象”是数据以及少量元信息,这和JAVA默认的序列化的本质区别,java默认的序列化的目的是语言层面的,将类、对象的所有信息都序列化了,也就是就算是不加载class的定义,也能根据序列化后的信息动态构建类的所有信息。而Kryo反序列化时,必须能加载类的定义,这样Kryo能节省大量的字节空间。

2、使用变长int、变长long存储int、long类型,大大节省空间。

3、元数据(字符串类型)使用缓存机制,重复出现的字符串使用int来存储,节省存储空间。

4、字符串类型使用UTF-8存储,但会使用ascii码进一步优化空间。