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


DefaultClassResolver#writeClass
public Registration writeClass (Output output, Class type) {if (type == null) {// @1 if (TRACE || (DEBUGoutput.writeVarInt(Kryo.NULL, true);return null;}Registration registration = kryo.getRegistration(type);// @2if (registration.getId() == NAME)// @3writeName(output, type, registration);else {if (TRACE) trace("kryo", "Write class " + registration.getId() + ": " + className(type));output.writeVarInt(registration.getId() + 2, true);// @4}return registration;}代码@ 1:如果类型为null , 则存储Kryo.NULL(0) , 使用变长int来存储 , 0在变长int中占用1个字节 。
代码@ 2:根据类型从kryo获取类注册信息 , 如果有调用kryo#public注册寄存器(类类型)方法 , 则返回其注册关系 。
代码@ 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) {// @2int 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表示为空 。
代码@ 2:如果classToNameId不为空(IdentityObjectIntMap ) , 根据类型获取nameId , 如果不为空并且从缓存中能获取到nameId , 则直接写入nameId , 而不是写入类名 , 这里指在一次序列化过程中 , 同一个类名例如(cn.uce.test.Test)只写入一次 , 其他级联(重复)出现时 , 为此分配一个ID , 进行缓存 , 具体可以从下面的代码中知道其必然 。
代码@ 3:首先分配一扩展递增的nameId 。
代码@ 4:如果classToNameId为空 , 则创建一个实例 。
代码@ 5:将类型与nameId进行缓存 。
代码@ 6:写入nameId 。 代码@ 7:写入type的全路径名 。
注意Kryo#writeClass , 一次序列化Class实例后会调用reset方法 , 最终会清除本次classToNameId , classToNameId并不能做一个全据的缓存的确实是 , 在不同的JVM虚拟机中 , 同一个类类型对应的nameId不一定相同 , 故无法实现共存 , 只能是作为一个优化 , 在一次类序列化中 , 如果存在同一个类型 , 则第一个写入类全路径名 , 后面出现的则使用id(int )来存储 , 节省空间 。
为了加深上述理解 , 我们再来看一下Class实例的反序列化:
DefaultClassResolver#readClass
public Registration readClass (Input input) { int classID = input.readVarInt(true);// @1 switch (classID) { case Kryo.NULL:// @2if (TRACE || (DEBUGreturn null; case NAME + 2: // Offset for NAME and NULL.// @3return 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 = http://kandian.youth.cn/index/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的实现: