关于如何使用Kryo完成序列化和反序列化
基于kryo完成序列化和反序列化1. Kryo的使用
Step01:定义mail类:package com.java.serializable; import java.io.Serializable; import java.util.Date; public class Mail implements Serializable{ private static final long serialVersionUID = 6599166688654530165L; private Integer id; private String title; private String content; private Date createdTime; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public Date getCreatedTime() { return createdTime; } public void setCreatedTime(Date createdTime) { this.createdTime = createdTime; } @Override public String toString() { return "Mail [id=" + id + ", title=" + title + ", content=" + content + ", createdTime=" + createdTime + "]"; } }
Step02:添加依赖 com.esotericsoftware kryo 5.0.0-RC4
Step03:编写测试类package com.java.serializable; import java.util.Date; import org.apache.tomcat.util.http.fileupload.ByteArrayOutputStream; import com.esotericsoftware.kryo.Kryo; import com.esotericsoftware.kryo.io.Input; import com.esotericsoftware.kryo.io.Output; public class TestSerializable06 { public static void main(String[] args) { Mail m=new Mail(); m.setId(100); m.setTitle("test"); m.setContent("this is test content"); m.setCreatedTime(new Date()); //基于Kryo框架将对象序列化 Kryo kryo=new Kryo(); //将默认类的自动注册功能关闭(默认会将类全名序列化) kryo.setRegistrationRequired(false); //kryo.register(Mail.class); //kryo.register(Date.class); ByteArrayOutputStream bos=//内置可扩容数组 new ByteArrayOutputStream(); Output output=new Output(bos); kryo.writeObject(output, m); //写入null时会报错 output.close(); System.out.println("序列化ok"); //基于Kryo框架将对象反序列化 byte[] data=bos.toByteArray(); Input input=new Input(data); Mail m2=kryo.readObject(input,Mail.class); //读出null时会报错 input.close(); System.out.println(m2); } }
结果:序列化ok Mail [id=100, title=test, content=this is test content, createdTime=Mon Nov 11 14:15:35 CST 2019]2. 工具类
可将如上序列化方法进行封装,写到序列化工具类中,例如:public class KryoSerializer { private static final ThreadLocal kryoLocal = ThreadLocal.withInitial(() -> { Kryo kryo = new Kryo(); kryo.setReferences(true);//检测循环依赖,默认值为true,避免版本变化显式设置 kryo.setRegistrationRequired(false);//默认值为true,避免版本变化显式设置 ((DefaultInstantiatorStrategy) kryo.getInstantiatorStrategy()) .setFallbackInstantiatorStrategy(new StdInstantiatorStrategy());//设定默认的实例化器 return kryo; }); public byte[] serialize(Object obj) { Kryo kryo = getKryo(); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); Output output = new Output(byteArrayOutputStream); kryo.writeClassAndObject(output, obj); output.close(); return byteArrayOutputStream.toByteArray(); } public T deserialize(byte[] bytes) { Kryo kryo = getKryo(); Input input = new Input(new ByteArrayInputStream(bytes)); return (T) kryo.readClassAndObject(input); } private Kryo getKryo() { return kryoLocal.get(); } }
编写测试类:package com.java.serializable; import java.io.IOException; import java.util.Date; public class TestSerializable07 { public static void main(String[] args)throws IOException { Mail m=new Mail(); m.setId(100); m.setTitle("test"); m.setContent("this is test content"); m.setCreatedTime(new Date()); //基于Kryo框架将对象序列化 byte[] array= KryoSerializer.serializable(m); System.out.println("序列化OK,array.length="+array.length); //基于Kryo框架将对象反序列化 Mail m2= KryoSerializer.deserialization(array,Mail.class); System.out.println(m2); } }
运行结果:序列化OK,array.length=49 Mail [id=100, title=test, content=this is test content, createdTime=Mon Nov 11 18:04:03 CST 2019]3. 两种读写方式
根据是否写入class类型分为两种方式, 这里特别指出这里的的class指的是读写对象的class, 如果读写的是有嵌套类型对象,则不管采用哪种方式, 子类型class都会序列化.3.1 只写实例信息知道class且对象不为nullkryo.writeObject(output, someObject); // ... SomeClass someObject = kryo.readObject(input, SomeClass.class);知道class且对象可能为nullkryo.writeObjectOrNull(output, someObject); // ... SomeClass someObject = kryo.readObjectOrNull(input, SomeClass.class);3.2 同时写入class类型和实例信息(RPC场景)
class未知且对象可能为null, 但这种场景, 会多占用空间. 这种方式是我们在RPC中应当使用的方式kryo.writeClassAndObject(output, object); // ... Object object = kryo.readClassAndObject(input); if (object instanceof SomeClass) { // ... }4. 相关配置
register类注册
kryo支持通过类注册, 注册会给每一个class一个int类型的Id相关联,这显然比类名称高效,但同时要求反序列化的时候的Id必须与序列化过程中一致。这意味着注册的顺序非常重要。kryo.register(SomeClassA.class); kryo.register(SomeClassB.class);
但是由于现实原因,同样的代码,同样的Class在不同的机器上注册编号任然不能保证一致,所以多机器部署时候反序列化可能会出现问题。
所以kryo默认会开启类注册(version:5.0.2),可以通过kryo.setRegistrationRequired(false)关闭, 关闭后Kryo会根据类型去loadClass关联kryo.setRegistrationRequired(false);//一般设置为false解决线程不安全
由于Kryo线程不安全, 意味着每次序列化和反序列化时都需要实例化一次, 或借助ThreadLocal来维护以保证其线程安全。private static final ThreadLocal kryos = new ThreadLocal() { protected Kryo initialValue() { Kryo kryo = new Kryo(); // configure kryo instance, customize settings return kryo; }; }; // Somewhere else, use Kryo Kryo k = kryos.get(); ...
或者使用kryo提供的pool:public KryoPool newKryoPool() { return new KryoPool.Builder(() -> { final Kryo kryo = new Kryo(); kryo.setInstantiatorStrategy(new Kryo.DefaultInstantiatorStrategy( new StdInstantiatorStrategy())); return kryo; }).softReferences().build(); }