Java序列化案例demo(包含Kryo、JDK原生、Protobuf、ProtoStuff以及hessian)
@[toc]
前言
本节配套案例代码:Java-Learn—Github地址
所有博客文件目录索引:博客目录索引(持续更新)
一、Kryo序列化(优先选择)
介绍
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的还快,但是序列化后的大小是大了几十倍了。
三、Protobuf序列化
介绍
protobuf—Github地址、protobuf-java
介绍:Protobuf 出自于 Google,性能还比较优秀,也支持多种语言,同时还是跨平台的。就是在使用中过于繁琐,因为你需要自己定义 IDL 文件和生成对应的序列化代码。这样虽然不然灵活,但是,另一方面导致 protobuf 没有序列化漏洞的风险。
Protobuf 包含序列化格式的定义、各种语言的库以及一个 IDL 编译器。正常情况下你需要定义 proto 文件,然后使用 IDL 编译器编译成你需要的语言。
正常流程:定义proto文件 -> 使用proto编译工具编译得到Java类 -> 使用该类来进行序列化与反序列化。
快速开始
引入依赖
<!-- protobuf -->
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.21.1</version>
</dependency>
使用步骤
1、首先安装proto的转换工具
安装配置好对应的path路径,测试下命令:
protoc --version
安装这个proto工具的目的是将对应自己编写的.proto文件转为一个Java类,使用这个Java类即可进行序列化与反序列化。
- 在一个.proto文件中可以写多个结构体都是可以的。
2、编写proto文件
使用ProtoBuf序列化数据—可查看对应protobuf对应java的类型
syntax = "proto3";
option java_package = "com.changlu.serialize.protobuf";
option java_outer_classname = "DemoModel";
message User {
string name = 1;
uint32 age = 2;
}
message Message {
uint32 message_type = 1;
}
- java_package:表示目标生成的包名路径。
- java_outer_classname:目标生成的工具类名称。
说明:若是觉得自己编写比较麻烦,那么我们可以自己先定义Java实体类,然后使用IDEA的插件将这个实体类转为对应proto的struct类型。
- 插件名称:pojo to proto;protobuf插件安装使用
3、准备好proto文件了之后,我们就要开始生成对应的工具类了
当前路径在main/proto/xxx下,我们想要输出到对应的main/java/com/changlu/serialize/protobuf/xxx中:
接着我们输入命令:
# -I:我自己编写的.proto 文件的位置。
# --java out 输出位置会以完整包名的形式出输出,我指定的是上级目录的java中,此时配合proto文件里的com.changlu.xxx,即可输出到我想要的目录下
# ./subscribeReq.proto:当前需要编译成java的proto文件名。
protoc -I=./ --java_out=../java ./User.proto
ok,此时我们就有了这个工具类DemoModel,对应的User、Message的实体类都在这个DemoModel类中有了,对于序列化与反序列化操作也在这个DemoModel中进行。
测试
//测试ProtoBuf
testProtobufSerialzize();
public void testProtobufSerialzize(){
//准备实体类
DemoModel.User.Builder userBuilder = DemoModel.User.newBuilder();
userBuilder.setAge(18);
userBuilder.setName("changlu");
DemoModel.User user = userBuilder.build();
System.out.println("=====开始序列化:Protobuf=====");
System.out.println("开始进行序列化");
long startTime = System.nanoTime();
//序列化
byte[] data = user.toByteArray();
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();
//反序列化
try {
System.out.println(" 反序列化后得到的对象为:" + DemoModel.User.parseFrom(data));
} catch (InvalidProtocolBufferException e) {
throw new RuntimeException("Serialization failed");
}
endTime = System.nanoTime();
System.out.println(" 反序列化时间为:" + (endTime - startTime) / 1000000000.0 + "秒");
System.out.println("=====结束序列化:Protobuf=====" + "\n");
}
四、ProtoStuff
介绍
由于 Protobuf 的易用性,它的哥哥 Protostuff 诞生了。
protostuff 基于 Google protobuf,但是提供了更多的功能和更简易的用法。虽然更加易用,但是不代表 ProtoStuff 性能更差。
快速开始
<!-- protobufstuff -->
<dependency>
<groupId>io.protostuff</groupId>
<artifactId>protostuff-core</artifactId>
<version>1.7.4</version>
</dependency>
<dependency>
<groupId>io.protostuff</groupId>
<artifactId>protostuff-runtime</artifactId>
<version>1.7.4</version>
</dependency>
<!-- protobufstuff -->
package com.changlu.serialize;
import io.protostuff.LinkedBuffer;
import io.protostuff.ProtostuffIOUtil;
import io.protostuff.Schema;
import io.protostuff.runtime.RuntimeSchema;
/**
* @Description: ProtoStuffSer序列化工具
* @Author: changlu
* @Date: 11:33 AM
*/
public class ProtoStuffSerializer implements Serializer{
//DEFAULT_BUFFER_SIZE:512
//每次序列化时使用缓冲区
private static final LinkedBuffer BUFFER = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
public byte[] serialize(Object obj) {
Class<?> clazz = obj.getClass();
Schema schema = RuntimeSchema.getSchema(clazz);
byte[] bytes;
try {
//序列化
bytes = ProtostuffIOUtil.toByteArray(obj, schema, BUFFER);
}finally {
BUFFER.clear();
}
return bytes;
}
public <T> T deserialize(byte[] bytes, Class<T> clazz) {
Schema<T> schema = RuntimeSchema.getSchema(clazz);
//反序列化
T obj = schema.newMessage();
ProtostuffIOUtil.mergeFrom(bytes, obj, schema);
return obj;
}
}
测试
//测试ProtoStuff
testSerialize(new ProtoStuffSerializer(), rpcResponse);
说明:这个序列化算法的反序列化是最快的目前来看。
五、hessian
介绍
hessian 是一个轻量级的,自定义描述的二进制 RPC 协议。hessian 是一个比较老的序列化实现了,并且同样也是跨语言的。
dubbo RPC 默认启用的序列化方式是 hessian2 ,但是,Dubbo 对 hessian2 进行了修改,不过大体结构还是差不多。
快速开始
<!-- hessian -->
<dependency>
<groupId>com.caucho</groupId>
<artifactId>hessian</artifactId>
<version>4.0.65</version>
</dependency>
package com.changlu.serialize;
import com.caucho.hessian.io.HessianInput;
import com.caucho.hessian.io.HessianOutput;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
/**
* @Description: Hessian序列化
* @Author: changlu
* @Date: 12:38 PM
*/
public class HessianSerializer implements Serializer{
@Override
public byte[] serialize(Object obj) {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();){
HessianOutput hessianOutput = new HessianOutput(baos);
//序列化
hessianOutput.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);){
HessianInput hessianInput = new HessianInput(bais);
//反序列化
return clazz.cast(hessianInput.readObject(clazz));
} catch (IOException e) {
throw new RuntimeException("Serialization failed");
}
}
}
测试
//测试Hession
testSerialize(new HessianSerializer(), rpcResponse);
总结
Kryo 是专门针对 Java 语言序列化方式并且性能非常好,如果你的应用是专门针对 Java 语言的话可以考虑使用,并且 Dubbo 官网的一篇文章中提到说推荐使用 Kryo 作为生产环境的序列化方式。
其他跨语言的序列化方式包含:Protobuf、 ProtoStuff、hessian如果有跨语言需求的话可以考虑使用,其他还包含Thrift,Avro 这些。
参考资料
[1] kryo:JavaGuide—Java 序列化详解、Kryo 序列化
[2] JDK原生:Java序列化——JDK序列化与Json序列化
[3] protobuf:Protobuf的生成及使用、Protobuf序列化、使用ProtoBuf序列化数据
[4] ProtoStuff:JavaGuide—Java 序列化详解
protobuf:Protobuf的生成及使用、Protobuf序列化、使用ProtoBuf序列化数据
[4] ProtoStuff:JavaGuide—Java 序列化详解
[5] Hession:JavaGuide—Java 序列化详解、hessian序列化原理分析
- 点赞
- 收藏
- 关注作者
评论(0)