测试kryo与jdk的ObjectOutputStream
kryo常用设置
InstantiatorStrategy即初始化策略,默认kryo在反序列化对象时需要对象的类有一个零参数构造器,该构造器可以是private的,kryo通过反射调用该构造器来实例化对象。如果没有这样一个构造器,就需要使用kryo.setInstantiatorStrategy(new StdInstantiatorStrategy())了,该策略通过jvm api创建对象,这会创建一个完全空的对象(即不执行任何代码中的初始化工作),如果对象在实例化时需要一些初始化操作(比如在构造代码块中执行一些计算逻辑),这种策略就不可行了。
比较好的策略是kryo.setInstantiatorStrategy(new Kryo.DefaultInstantiatorStrategy(new StdInstantiatorStrategy()));,即先尝试通过零参数构造实例化对象,如果类中没有零参数构造器,则会使用StdInstantiatorStrategy策略。
References即引用,对A对象序列化时,默认情况下kryo会在每个成员对象第一次序列化时写入一个数字,该数字逻辑上就代表了对该成员对象的引用,如果后续有引用指向该成员对象,则直接序列化之前存入的数字即可,而不需要再次序列化对象本身。这种默认策略对于成员存在互相引用的情况较有利,否则就会造成空间浪费(因为没序列化一个成员对象,都多序列化一个数字),通常情况下可以将该策略关闭,kryo.setReferences(false);
Registration即注册,kryo在序列化对象时,首先会序列化其类的全限定名,由于我们通常序列化的对象都是有限范围内的类的实例,这样重复序列化同样的类的全限定名是低效的。通过注册kryo可以将类的全限定名抽象为一个数字,即用一个数字代表全限定名,这样就要高效一些。kryo.register(SomeClass.class);,注册方法的完整签名为public Registration register (Class type, Serializer serializer, int id),我们通常只需要使用其重载方法即可public Registration register (Class type),serializer和id在kryo内部会指定。
PS:使用kryo序列化时,可以使用transient关键字忽略某字段。
序列化方法
kryo大体有三种序列化方法,每种方式都有其优势和劣势。
1,kryo.writeObject,这种方法只会序列化对象实例,而不会记录对象所属类的任何信息。优势是最节省空间,劣势是在反序列化时,需要提供类作为模板才能顺利反序列。反序列化时使用readObject。
2,直接kryo.writeClassAndObject,这种方法会先将对象所属类的全限定名序列化,然后再依次序列化对象实例的成员。优势是完全动态序列化,整个kryo周期都不需要提供类信息。反序列化时使用readClassAndObject
3,先注册,再kryo.writeClassAndObject,这种方式时最理想的,其结合了前两种优势,又有效规避了劣势,事先将需要序列化的类注册给kryo(此时类和唯一id绑定),之后使用writeClassAndObject序列化时,只会序列化注册id,而不会序列化类的全限定名了,这样大大节省了空间(通常只比writeObject多一个字节)。反序列化时使用readClassAndObject。注意序列化和反序列的kryo的注册信息应当保持一致。
Example
1 public class Simple { 2 private String name; 3 private int age; 4 5 6 public String getName() { 7 return name; 8 } 9 10 public void setName(String name) { 11 this.name = name; 12 } 13 14 public int getAge() { 15 return age; 16 } 17 18 public void setAge(int age) { 19 this.age = age; 20 } 21 22 static Simple getSimple() { 23 Simple simple = new Simple(); 24 simple.setAge(10); 25 simple.setName("zhang3"); 26 return simple; 27 } 28 }
1 Kryo kryo = new Kryo(); 2 kryo.setReferences(false); 3 kryo.setRegistrationRequired(false); 4 kryo.setInstantiatorStrategy(new StdInstantiatorStrategy()); 5 Output output = new Output(new FileOutputStream("file.bin")); 6 kryo.writeClassAndObject(output, Simple.getSimple()); 7 output.close();
红色框内,第一个字节代表classId,也就是kryo中注册的id,01表示未注册,第二个字节代表nameId;
绿色框内为类的全限定名;
紫色框内,唯一的一个字节14表示age属性值10(cryo为了优化存储,定义了自己的映射关系);
粉色框内表示字符串“zhang3”,其中字符串“zhang”可以与asc码一一对应,最后一个字符“3”这里为B3就有些匪夷所思了,与14表示10类似,kryo对字符串也做了优化,这样可以省去表示长度的字节序列(具体优化策略就不探究了)。
如果序列化时,Sample中字段的顺序是age、name,反序列化时,Sample中字段的顺序是name、age,这样会不会有问题呢?
不会有问题,kryo在序列化、反序列前对字段进行了排序,kryo的序列化、反序列化顺序与字段声明顺序无关。