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)