如上所述,Kryo 是一个针对Java的高效序列化工具,在序列化和反序列 Java 对象时速度在某些方面更优于ProtoBuf (参考自wiki). 其被广泛应用于很多Java的框架中,Twitter Chill是对Kryo应用于Scala对象的补充。
序列化相互引用的对象
序列化对象也就必然需要序列化对象的引用,而对于相互引用的对象即 objectA reference to objectB, objectB reference to objectA, 由于对象之间相互引用形成一个环,此时的序列化就相对来说稍微有点复杂。
By default, each appearance of an object in the graph after the first is stored as an integer ordinal. This allows multiple references to the same object and cyclic graphs to be serialized. This has a small amount of overhead and can be disabled to save space if it is not needed.
以上Kryo中的做法,当kryo.setReferences(true)时,用一个数组来存储对象,只有首次出现的对象放在数组中,后续出现的对象只存储一个数组中位置即可,这减少了环形引用对象的序列化大小。但是对于不存在环形引用的对象这是不必要的,因为会增加部分开销。刚开始读这段时没太理解,特意在StackOverFlow上问了下,通过@JBNizet的解释豁然开朗。
自定义Serializer
Kryo提供了很多默认的Serializer, 但是有时往往难以满足需求,需要自定义Serializer。在自定义Serializer时需要注意的是:
The Kryo instance can be used to write and read nested objects. If Kryo is used to read a nested object in
read()thenkryo.reference()must first be called with the parent object if it is possible for the nested object to reference the parent object.
也就是说当存储在环型引用时,在反序列话时需要调用kryo.reference(obj),参考以下例子:
People.java
public class People { String name; People friend; public People() {} public People(String name) { this.name = name; } public void setFriend(People p) { friend = p; } public String toString() { return "Friend: " + friend.name; } }
PeopleSerializer.java
public class PeopleSerializer extends Serializer<People>{ public void write(Kryo kryo, Output output, People object) { output.writeString(object.name); kryo.writeObject(output, object.friend); } public People read(Kryo kryo, Input input, Class<People> type) { String name = input.readString(); People p = new People(name); kryo.reference(p); // 如将此行注释,则type.friend为null,下面的Main会NullpointException People friend = kryo.readObject(input, People.class); p.setFriend(friend); return p; } }
Main.java
public static void main(String[] args) { People tom = new People("Tom"); People bob = new People("Bob"); tom.setFriend(bob); bob.setFriend(tom); Kryo kryo = new Kryo(); kryo.register(People.class, new PeopleSerializer()); String path = "/Users/lxy/IdeaProjects/firstProject/data/people"; Output output = null; Input input = null; try { OutputStream outputStream = new FileOutputStream(path); output = new Output(outputStream); kryo.writeObject(output, tom); output.flush(); InputStream inputStream = new FileInputStream(path); input = new Input(inputStream); tom = kryo.readObject(input, People.class); System.out.println(tom); System.out.println(tom.friend); } catch (FileNotFoundException e) { e.printStackTrace(); } finally { if (output != null) { IOUtils.closeQuietly(output); } if (input != null) { IOUtils.closeQuietly(input); } } }
同时在序列化环形引用时需保证kryo.setReferences(true),并且在SelfSerializer.read()中kryo.reference(obj)。
kryo.setReferences()对序列化的大小影响
同样是上文中例子,只是去除了friend只保留一个name。
Main.java
kryo.setReferences(false); OutputStream outputStream = new FileOutputStream(path); output = new Output(outputStream); kryo.writeObject(output, tom); output.flush(); File file = new File(path); long size = file.length(); System.out.println(size); // kryo.setReference(true) size:4, kryo.setReference(false) // size:3
从上面的例子可以看出,当对象的引用中不存在环形引用时,调用kryo.setReference(false)还是很有必要的,并且由于查找对象引用数组获取对应的对象引用也相当于一个间接查询。