一、Kryo序列化(优先选择)
介绍
kryo-Gihub仓库地址
Kryo 是一个高性能的序列化/反序列化工具,由于其变长存储特性并使用了字节码生成机制,拥有较高的运行速度和较小的字节码体积,并且Kryo 已经是一种非常成熟的序列化实现了,已经在 Twitter、Groupon、Yahoo 以及多个著名开源项目(如 Hive、Storm)中广泛的使用。
基于Java的快速高效的对象序列化框架,旨在提供快速、高效和易用的API。无论文件、数据库或网络数据Kryo都可以随时完成序列化。Kryo还可以执行自动深拷贝(克隆)、浅拷贝(克隆),这是对象到对象的直接拷贝,非对象→字节→对象的拷贝。支持互相引用,比如类A引用类B,类B引用类A,可以正确地反序列化。
说明:对于Kryo需要你进行提前进行手动注册class类,这种方式能够更够让序列化后的内容更小,序列化更快,当然你也可以不选择手动注册,那么内容会稍微大一些。
快速开始
<dependency> <groupId>com.esotericsoftware.kryo</groupId> <artifactId>kryo5</artifactId> <version>5.3.0</version> </dependency>
pojo类:
package com.changlu.serialize.pojo;
import java.io.Serializable;
/** * @ClassName Message * @Author ChangLu * @Date 6/14/2022 8:39 PM * @Description 消息类 */ public class Message implements Serializable { private int messageType; public int getMessageType() { return messageType; } public void setMessageType(int messageType) { this.messageType = messageType; } }
package com.changlu.serialize.pojo;
import java.io.Serializable;
/** * @ClassName User * @Author ChangLu * @Date 6/14/2022 8:45 PM * @Description 用户类 */ public class User implements Serializable { private String name; private Integer age; public User(){ } public User(String name, Integer age) { this.name = name; this.age = age; } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
package com.changlu.serialize.pojo;
/** * @ClassName RPCResponse * @Author ChangLu * @Date 6/14/2022 8:39 PM * @Description RPC响应实体类 */ public class RPCResponse<T> extends Message{ private T data; public T getData() { return data; } public void setData(T data) { this.data = data; } @Override public String toString() { return "RPCResponse{" + "data=" + data + '}'; } }
序列化接口以及实现类:
package com.changlu.serialize; public interface Serializer { /** * 序列化 * * @param obj 要序列化的对象 * @return 字节数组 */ byte[] serialize(Object obj); /** * 反序列化 * * @param bytes 序列化后的字节数组 * @param clazz 目标类 * @param <T> 类的类型。举个例子, {@code String.class} 的类型是 {@code Class<String>}. * 如果不知道类的类型的话,使用 {@code Class<?>} * @return 反序列化的对象 */ <T> T deserialize(byte[] bytes, Class<T> clazz); }
package com.changlu.serialize; import com.esotericsoftware.kryo.kryo5.Kryo; import com.esotericsoftware.kryo.kryo5.io.Input; import com.esotericsoftware.kryo.kryo5.io.Output; import com.esotericsoftware.kryo.kryo5.objenesis.strategy.StdInstantiatorStrategy; import com.esotericsoftware.kryo.kryo5.util.DefaultInstantiatorStrategy; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; /** * @ClassName KryoSerializer * @Author ChangLu * @Date 6/14/2022 8:33 PM * @Description Kryo序列化工具 */ public class KryoSerializer implements Serializer{ //由于Kryo是线程不安全的,所以我们这里使用ThreadLocal来解决线程安全问题 public static ThreadLocal<Kryo> kryoThreadLocal = ThreadLocal.withInitial(()->{ Kryo kryo = new Kryo(); kryo.setReferences(true);//检测循环依赖,默认值为true,避免版本变化显示设置 //方式一:设置无需注册,那么之后就无需对需要进行序列号的类进行注册(性能略差) //kryo.setRegistrationRequired(false);//默认值为true,避免版本变化显示设置 ((DefaultInstantiatorStrategy)kryo.getInstantiatorStrategy()) .setFallbackInstantiatorStrategy(new StdInstantiatorStrategy());//设置默认的实例化器 //方式二:由于默认是需要进行注册的,若是不设置为false,那么就需要进行手动注册class类 kryo.register(User.class); kryo.register(RPCResponse.class); return kryo; }); @Override public byte[] serialize(Object obj) { try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); final Output output = new Output(baos) ) { Kryo kryo = kryoThreadLocal.get(); //进行序列化 kryo.writeObject(output, obj); kryoThreadLocal.remove(); return output.toBytes(); } catch (IOException e) { throw new RuntimeException("Serialization failed"); } } @Override public <T> T deserialize(byte[] bytes, Class<T> clazz) { try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes); Input input = new Input(byteArrayInputStream)) { Kryo kryo = kryoThreadLocal.get(); Object obj = kryo.readObject(input, clazz); kryoThreadLocal.remove(); return clazz.cast(obj); } catch (IOException e) { throw new RuntimeException("Serialization failed"); } } }
测试
当前Kryo采用的是注册class的方式来进行反序列化的:
package com.changlu.serialize; import com.changlu.serialize.pojo.RPCResponse; import com.changlu.serialize.pojo.User; import org.junit.Test; /** * @Description: 序列号测试工具 * @Author: changlu * @Date: 9:31 AM */ public class SerializerTest { @Test public void test(){ //测试对象 RPCResponse<User> rpcResponse = new RPCResponse<>(); rpcResponse.setData(new User("changlu", 123)); //测试kryo序列化 testSerialize(new KryoSerializer(), rpcResponse); } public <T> void testSerialize(Serializer serializer, T t) { System.out.println(String.format("=====开始序列化:%s=====", serializer.getClass())); System.out.println("开始进行序列化"); long startTime = System.nanoTime(); //序列化 byte[] data = serializer.serialize(t); long endTime = System.nanoTime(); System.out.println(" 序列化时间为:" + (endTime - startTime) / 1000000000.0 + "秒"); System.out.println(" 序列化后的内容为:" + new String(data)); System.out.println(" 序列化后的长度为:" + data.length); System.out.println("开始进行反序列化"); startTime = System.nanoTime(); //反序列化 System.out.println(" 反序列化后得到的对象为:" + serializer.deserialize(data, t.getClass())); endTime = System.nanoTime(); System.out.println(" 反序列化时间为:" + (endTime - startTime) / 1000000000.0 + "秒"); System.out.println(String.format("=====结束序列化:%s=====", serializer.getClass()) + "\n"); } }
二、JDK原生序列化
介绍
介绍:Java类通过实现Serializable接口来实现该类对象的序列化,这个接口非常特殊,没有任何方法,只起标识作用。Java序列化保留了对象类的元数据(如类、成员变量、继承类信息),以及对象数据等,兼容性最好,但不支持跨语言,而且性能一般。
对于serialVersionUID 的说明:序列化号 serialVersionUID 属于版本控制的作用。序列化的时候 serialVersionUID 也会被写入二级制序列,当反序列化时会检查 serialVersionUID 是否和当前类的 serialVersionUID 一致。如果 serialVersionUID 不一致则会抛出 InvalidClassException 异常。强烈推荐每个序列化类都手动指定其 serialVersionUID,如果不手动指定,那么编译器会动态生成默认的序列化号
一般不使用原生的JDK序列化:
不支持跨语言调用 : 如果调用的是其他语言开发的服务的时候就不支持了。
性能差 :相比于其他序列化框架性能更低,主要原因是序列化之后的字节数组体积较大,导致传输成本加大。
快速开始
基于目录一中的包来进行实现即可,只要实现serialize的接口,编写序列化与反序列化方法即可!
package com.changlu.serialize; import java.io.*; /** * @Description: JDK原生序列化 * @Author: changlu * @Date: 9:54 AM */ public class JdkSerializer implements Serializer{ @Override public byte[] serialize(Object obj) { try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); ) { oos.writeObject(obj); return baos.toByteArray(); } catch (IOException e) { throw new RuntimeException("Serialization failed"); } } @Override public <T> T deserialize(byte[] bytes, Class<T> clazz) { try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes); ObjectInputStream ois = new ObjectInputStream(bais); ) { return (T) ois.readObject(); } catch (IOException | ClassNotFoundException e) { throw new RuntimeException("Serialization failed"); } } }
测试
基于一中的测试添加一条代码即可来进行测试:
//测试JDK原生序列化工具 testSerialize(new JdkSerializer(), rpcResponse);
发现:JDK的序列化与反序列化能够比Kryo的还快,但是序列化后的大小是大了几十倍了。