栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Java序列化案例demo(包含Kryo、JDK原生、Protobuf、ProtoStuff以及hessian)

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Java序列化案例demo(包含Kryo、JDK原生、Protobuf、ProtoStuff以及hessian)

文章目录
  • 前言
  • 一、Kryo序列化(优先选择)
    • 介绍
    • 快速开始
    • 测试
  • 二、JDK原生序列化
    • 介绍
    • 快速开始
    • 测试
  • 三、Protobuf序列化
    • 介绍
    • 快速开始
    • 测试
  • 四、ProtoStuff
    • 介绍
    • 快速开始
    • 测试
  • 五、hessian
    • 介绍
    • 快速开始
    • 测试
  • 总结
  • 参考资料

前言

本节配套案例代码:Java-Learn—Github地址

所有博客文件目录索引:博客目录索引(持续更新)

一、Kryo序列化(优先选择) 介绍

kryo-Gihub仓库地址

Kryo 是一个高性能的序列化/反序列化工具,由于其变长存储特性并使用了字节码生成机制,拥有较高的运行速度和较小的字节码体积,并且Kryo 已经是一种非常成熟的序列化实现了,已经在 Twitter、Groupon、Yahoo 以及多个著名开源项目(如 Hive、Storm)中广泛的使用。

基于Java的快速高效的对象序列化框架,旨在提供快速、高效和易用的API。无论文件、数据库或网络数据Kryo都可以随时完成序列化。Kryo还可以执行自动深拷贝(克隆)、浅拷贝(克隆),这是对象到对象的直接拷贝,非对象→字节→对象的拷贝。支持互相引用,比如类A引用类B,类B引用类A,可以正确地反序列化。

说明:对于Kryo需要你进行提前进行手动注册class类,这种方式能够更够让序列化后的内容更小,序列化更快,当然你也可以不选择手动注册,那么内容会稍微大一些。

快速开始


    com.esotericsoftware.kryo
    kryo5
    5.3.0

pojo类:

package com.changlu.serialize.pojo;

import java.io.Serializable;


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;


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;


public class RPCResponse 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 {
    
    byte[] serialize(Object obj);

    
     T deserialize(byte[] bytes, Class 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;


public class KryoSerializer implements Serializer{

    //由于Kryo是线程不安全的,所以我们这里使用ThreadLocal来解决线程安全问题
    public static ThreadLocal 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 deserialize(byte[] bytes, Class 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;


public class SerializerTest {

    @Test
    public void test(){
        //测试对象
        RPCResponse rpcResponse = new RPCResponse<>();
        rpcResponse.setData(new User("changlu", 123));
        //测试kryo序列化
        testSerialize(new KryoSerializer(), rpcResponse);
    }

    public  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序列化:

  1. 不支持跨语言调用 : 如果调用的是其他语言开发的服务的时候就不支持了。
  2. 性能差 :相比于其他序列化框架性能更低,主要原因是序列化之后的字节数组体积较大,导致传输成本加大。
快速开始

基于目录一中的包来进行实现即可,只要实现serialize的接口,编写序列化与反序列化方法即可!

package com.changlu.serialize;

import java.io.*;


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 deserialize(byte[] bytes, Class 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类 -> 使用该类来进行序列化与反序列化。

快速开始

引入依赖



    com.google.protobuf
    protobuf-java
    3.21.1

使用步骤

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 介绍

protostuff-github

由于 Protobuf 的易用性,它的哥哥 Protostuff 诞生了。

protostuff 基于 Google protobuf,但是提供了更多的功能和更简易的用法。虽然更加易用,但是不代表 ProtoStuff 性能更差。

快速开始


    io.protostuff
    protostuff-core
    1.7.4


    io.protostuff
    protostuff-runtime
    1.7.4


package com.changlu.serialize;

import io.protostuff.LinkedBuffer;
import io.protostuff.ProtostuffIOUtil;
import io.protostuff.Schema;
import io.protostuff.runtime.RuntimeSchema;


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 deserialize(byte[] bytes, Class clazz) {
        Schema 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 进行了修改,不过大体结构还是差不多。

快速开始


    com.caucho
    hessian
    4.0.65

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;


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 deserialize(byte[] bytes, Class 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序列化原理分析

我是长路,感谢你的耐心阅读。如有问题请指出,我会积极采纳!
欢迎关注我的公众号【长路Java】,分享Java学习文章及相关资料
Q群:851968786 我们可以一起探讨学习
注明:转载可,需要附带上文章链接

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/976002.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号