Kryo简介及代码阅读笔记

简介: 更新:2012-08-01版本 2.16长时间运行可能会导致OOM,版本2.18有bug,不能正确序列化map和collection。

更新:2012-08-01

版本 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)。

    private int depth, maxDepth = Integer.MAX_VALUE, nextRegisterID;
private final ArrayList writtenObjects = new ArrayList();
private final ArrayList readObjects = new ArrayList();

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

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

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


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

    for (int i = 0, n = writtenObjects.size(); i < n; i++) {
    if (writtenObjects.get(i) == object) {
        if (DEBUG)
            debug("kryo", "Write object reference " + i + ": "
                    + string(object));
        output.writeInt(i + 1, true); // + 1 because 0 means null.
        return true;
    }
}
// Only write the object the first time encountered in object graph.
output.writeInt(writtenObjects.size() + 1, true);
writtenObjects.add(object);

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

    if (--id < readObjects.size()) {
    Object object = readObjects.get(id);
    if (DEBUG)
        debug("kryo", "Read object reference " + id + ": "
                + string(object));
    return object;
}



注册:

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

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

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


注解annotation:

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


实现原理:

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


代码阅读笔记:

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

    private final IntMap<Registration> idToRegistration = new IntMap();
private final ObjectMap<Class, Registration> classToRegistration = new ObjectMap();
private final IdentityObjectIntMap<Class> classToNameId = new IdentityObjectIntMap();
private final IntMap<Class> nameIdToClass = new IntMap();
private int nextNameId;  //不断增长,分新的Class分配一个新的id,即Registration中的id


每一个类对应一个Registration:

    public class Registration {
    private final Class type;
    private final int id;         //这个要注意,每一个类都有一个唯一的id,这个id是从0开始不断增长的
    private Serializer serializer;
    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写到输出流中。

    private CachedField[] fields = new CachedField[0];
                                                     
public class CachedField<X> {
    final Field field;
    Class fieldClass;
    Serializer serializer;
    boolean canBeNull;
    int accessIndex = -1;
}


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

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


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

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

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


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

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

    buffer[position - 1] |= 0x80;

不知为何。。

在readString的时候也会判断:

    if ((b & 0x80) == 0) {
// ascii


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


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

为每一个类分配一个id

实现了自己的IntMap

代码中一些取巧的地方:

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

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


注意事项:

实现KryoSerializable接口

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

    @Override
public void write(Kryo kryo, Output output) {
    // TODO Auto-generated method stub
    kryo.writeObject(output, this);
}
                             
@Override
public void read(Kryo kryo, Input input) {
    // TODO Auto-generated method stub
    kryo.readObject(input, this.getClass());
}

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

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

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

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



相关文章
|
XML Oracle 关系型数据库
GeoTools之入门简介
GeoTools是一套开源的用java言语开发的GIS开发工具包,可以基于GeoTools开发符合标准的地理信息系统。GeoTools 提供了 OGC(Open Geospatial Consortium)规范的一个实现来作为他们的开发。
1556 0
GeoTools之入门简介
|
26天前
|
存储 安全 Java
【Java技术专题】「Guava开发指南」手把手教你如何进行使用Guava工具箱进行开发系统实战指南(不可变集合篇)
【Java技术专题】「Guava开发指南」手把手教你如何进行使用Guava工具箱进行开发系统实战指南(不可变集合篇)
30 1
|
26天前
|
Java API Apache
【Java技术专题】「Guava开发指南」手把手教你如何进行使用Guava工具箱进行开发系统实战指南(基础编程篇)
【Java技术专题】「Guava开发指南」手把手教你如何进行使用Guava工具箱进行开发系统实战指南(基础编程篇)
43 0
|
3月前
|
Java
从零开始学习 Java:简单易懂的入门指南之HashMap及TreeMap源码解读(二十四)
从零开始学习 Java:简单易懂的入门指南之HashMap及TreeMap源码解读(二十四)
|
3月前
|
Java 编译器 C++
重温经典《Thinking in java》第四版之第九章 接口(四十六)
重温经典《Thinking in java》第四版之第九章 接口(四十六)
17 0
|
11月前
|
存储 算法 Java
java 一文讲透API [常用类 + 常用工具](20万字博文)
java API——常用类 和 API——常用工具 全面总结。20万字深度讲解。
228 0
|
Java 容器
java 增强for和迭代器 万字详解 (粗俗易懂)
java 集合讲解篇之 增强for和迭代器,内容分享!
9430 1
java 增强for和迭代器 万字详解 (粗俗易懂)
|
安全 Java 编译器
Java泛型——简介
Java泛型——简介
70 0
|
存储 JSON Java
深入浅出FlatBuffers原理
FlatBuffers 是一个开源的、跨平台的、高效的、提供了多种语言接口的序列化工具库。实现了与 Protocal Buffers 类似的序列化格式。主要由 Wouter van Oortmerssen 编写,并由 Google 开源。本文将基于高德地图数据编译增量发布使用了FlatBuffers序列化工具,为大家分享FlatBuffers的原理。
深入浅出FlatBuffers原理
|
存储 JSON JavaScript
序列化和反序列化 | 手把手教你入门Python之七十五
设计⼀套协议,按照某种规则,把内存中的数据转换为字节序列,保存到⽂件,这就是序列化,反之,从⽂件的字节序列恢复到内存中,就是反序列化。
序列化和反序列化 | 手把手教你入门Python之七十五