欢迎您光临本小站。希望您在这里可以找到自己想要的信息。。。

Kryo分析与运用

架构&设计模式 water 4447℃ 0评论

版本 2.16长时间运行可能会导致OOM,版本2.18有bug,不能正确序列化map和collection。

真是悲剧,所用的每一个版本都有bug。不过从代码来看,作者有时的确比较随便。。测试用例也少。。(比起msgpack少多了)

========================================

Kryo官方网站:https://code.google.com/p/kryo/

优点:

    速度快!见https://github.com/eishay/jvm-serializers/wiki/Staging-Results

    支持互相引用,比如类A引用类B,类B引用类A,可以正确地反序列化。

    支持多个引用同一个对象,比如多个类引用了同一个对象O,只会保存一份O的数据。

    支持一些有用的注解,如@Tag@Optional

    支持忽略指定的字段。

    支持null

    代码入侵少

    代码比较简法(比起msgpack,少得多)

缺点:

    bug多 2.12,2.14都有bug

    文档比较少,有些使用方法要看代码才能理解,最新版2.14bug,不能正确反序列化map类型。

    不是跨语言的解决方案

    貌似每一个类都要注册下,不然只能用writeClassAndObject和readClassAndObject函数。

    类要有一个空的构造函数,不然不能序列化。

    (如果这个构造函数里调用了别的资源,而这个资源没有初始化,那么就悲剧了。)

   可以通过实现KryoSerializable接口来避免这个问题。。同样不能解决这个问题

   Java自带的则不用调用这个构造函数。

       msgpack同样有这个问题。

接口:

    KryoSerializable

    KryoCopyable

实现忽略指定的字段

  1. 使用transient关键字

  2. 使用Context结合@Optional注解

  3. 使用@Tag注解(很麻烦)

  4. 实现KryoSerializable接口(比较麻烦,相当于手写代码)

引用:

可以用setReferences(boolean )函数来设置,默认是打开的。

引用的实现原理:

原本以为要实现引用是个比较麻烦的事,因为一想到引用,头脑中就出现一个图。。但在看了代码后,发现是比较简单的。

在Kryo类中有一个writtenObjects的ArrayList,记录已写入的对象。有一个readObjects来记录已写入的对象。

另外有个depth来记录深度,每写一个对象时depth++,当写完时depth–,当depth == 0时,调用reset函数,清空writtenObjects和

比如写一个大对象,这个对象有很多成员,每一个成员都是一个对象,而成员之间有可能用引用关系(A引用了B,B也引用了A)。

[java] view plaincopy

  1.     private int depth, maxDepth = Integer.MAX_VALUE, nextRegisterID;  

  2. private final ArrayList writtenObjects = new ArrayList();  

  3. private final ArrayList readObjects = new ArrayList();  

每当写一个对象时,都到里面去检查有没有这个对象,如果有的话,就只写一个int即可。这个int是要表明这个对象当前在的位置即可。

因为当反序列化时,可以据读到的int,正确地从readObjects取回对象。

如果没有,则在输出流中写入writtenObjects的size()+1,再把这个对象放到writtenObjects中。


序列化时,写入引用的对象在writtenObjects中的位置:

[java] view plaincopy

  1.     for (int i = 0, n = writtenObjects.size(); i < n; i++) {  

  2.     if (writtenObjects.get(i) == object) {  

  3.         if (DEBUG)  

  4.             debug("kryo""Write object reference " + i + ": "  

  5.                     + string(object));  

  6.         output.writeInt(i + 1true); // + 1 because 0 means null.  

  7.         return true;  

  8.     }  

  9. }  

  10. // Only write the object the first time encountered in object graph.  

  11. output.writeInt(writtenObjects.size() + 1true);  

  12. writtenObjects.add(object);  

反序列化时,据id从readObjects得到正确的对象:

[java] view plaincopy

  1.     if (–id < readObjects.size()) {  

  2.     Object object = readObjects.get(id);  

  3.     if (DEBUG)  

  4.         debug("kryo""Read object reference " + id + ": "  

  5.                 + string(object));  

  6.     return object;  

  7. }  

注册:

貌似每一个类都要注册下,不然只能用writeClassAndObject和readClassAndObject函数。

注册的顺序不能乱!!因为是每一个类都有一个id,而这个id是增长的!

可以设置registrationRequired,防止没有注册的情况!


注解annotation:

貌似只有四个:@Optional,@Tag,@DefaultSerializer,@NotNull

实现原理:

每注册一个类,都有一个id,由一个IntMap的hashMap来保存(TODO,研究下这个东东的实现)

代码阅读笔记:

在Kryo类中有以下的成员,简单来看,就是一些HashMap,用来存放id和Class,Class和id,id和Registration,Class和Registration之间的对应关系

[java] view plaincopy

  1.     private final IntMap<Registration> idToRegistration = new IntMap();  

  2. private final ObjectMap<Class, Registration> classToRegistration = new ObjectMap();  

  3. private final IdentityObjectIntMap<Class> classToNameId = new IdentityObjectIntMap();  

  4. private final IntMap<Class> nameIdToClass = new IntMap();  

  5. private int nextNameId;  //不断增长,分新的Class分配一个新的id,即Registration中的id  

每一个类对应一个Registration:

[java] view plaincopy

  1. public class Registration {  

  2. private final Class type;  

  3. private final int id;         //这个要注意,每一个类都有一个唯一的id,这个id是从0开始不断增长的  

  4. private Serializer serializer;  

  5. private ObjectInstantiator instantiator;  //复制对象时候用  

直接看这个类的成员,就大概能明白它是怎样回事了。

要注意一点,Registration中的id很重要,可以说是和别的序列化方案相比,高效之处。

在调用Kryo.writeClass(Output output, Class type)函数时,

先查找到这个类的Registration,得到Serializer,再调用write (Kryo kryo, Output output,
T object)写到输出流中。


如果没有找到的话,则为这个类生成一个Registration,并放到Kryo类中的对应的HashMap中。

再来说下Serializer

默认是FieldSerializer,在生成Registration中,如果为这个类找不到Serializer(到defaultSerializers中找),

则会构造一个FieldSerializer。

FieldSerializer实际是有一个数组存放了每一个field的信息,当调用write
(Kryo kryo, Output output, T object)函数时,则历遍所有的field,把每一个field写到输出流中。

[java] view plaincopy

  1. private CachedField[] fields = new CachedField[0];  

  2.                                                    

  3. ic class CachedField<X> {  

  4. final Field field;  

  5. Class fieldClass;  

  6. Serializer serializer;  

  7. boolean canBeNull;  

  8. int accessIndex = –1;  


Kryo有两种模式,一种是先注册(regist),再写对象,即writeObject函数,实际上如果不先注册,在写对象时也会注册,并为class分配一个id。

注意,如果是rpc,则必须两端都按同样的顺序注册,否则会出错,因为必须要明确类对应的唯一id。


另一种是写类名及对象,即writeClassAndObject函数。

writeClassAndObject函数是先写入(-1
+ 2)(一个约定的数字)
,再写入类ID(第一次要先写-1,再写类ID + 类名),写入引用关系(见引用的实现),最后才写真正的数据)。

注意每一次writeClassAndObject调用后信息都会清空,所以不用担心和client交互时会出错。


代码中其它有意思的地方:

在writeString函数中先判断是不是ascii即,所有都<127,如果是在写string的最后一个字符,会执行这个:

[java] view plaincopy

  1. buffer[position – 1] |= 0x80;  

不知为何。。

在readString的时候也会判断:

[java] view plaincopy

  1. if ((b & 0x80) == 0) {  

  2. scii  

是因为这个http://topic.csdn.net/t/20040416/10/2972001.html ?所谓的双字节的?

为什么比其它的序列化方案要快?

为每一个类分配一个id

实现了自己的IntMap

代码中一些取巧的地方:

利用变量memoizedRegistration和memoizedType记录上一次的调用writeObject函数的Class,则如果两次写入同一类型时,可以直接拿到,不再查找HashMap。

这个也许是为什么在测试中kryo要比其它类库要快的原因之一。

注意事项:

实现KryoSerializable接口

像下面这样实现是错误的。

[java] view plaincopy

  1.     @Override  

  2. public void write(Kryo kryo, Output output) {  

  3.     // TODO Auto-generated method stub  

  4.     kryo.writeObject(output, this);  

  5. }  

  6.                                

  7. @Override  

  8. public void read(Kryo kryo, Input input) {  

  9.     // TODO Auto-generated method stub  

  10.     kryo.readObject(input, this.getClass());  

  11. }  

实际上只写入了默认构造函数的内容!

原因是在生成Registration时已在writtenObjects中写入了这个类,所以在Kryo类中的writtenObjects中已有这个类,所以在调用write函数时,如果是用下面的代码,则会以为这个类是已写过的,所以直接写了一个1和它的id!!

实际上如果实现了KryoSerializable接口,最终是这个类来调用接口的write函数:KryoSerializableSerializer

正确的写法是写入每一个成员,在read函数中把数据读出,再赋值给成员。

转载请注明:学时网 » Kryo分析与运用

喜欢 (0)or分享 (0)

您必须 登录 才能发表评论!