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

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

一、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的还快,但是序列化后的大小是大了几十倍了。


相关文章
|
1天前
|
安全 Java 编译器
JDK 10中的局部变量类型推断:Java编程的简化与革新
JDK 10引入的局部变量类型推断通过`var`关键字简化了代码编写,提高了可读性。编译器根据初始化表达式自动推断变量类型,减少了冗长的类型声明。虽然带来了诸多优点,但也有一些限制,如只能用于局部变量声明,并需立即初始化。这一特性使Java更接近动态类型语言,增强了灵活性和易用性。
78 53
|
7天前
|
jenkins Java 测试技术
如何使用 Jenkins 自动发布 Java 代码,通过一个电商公司后端服务的实际案例详细说明
本文介绍了如何使用 Jenkins 自动发布 Java 代码,通过一个电商公司后端服务的实际案例,详细说明了从 Jenkins 安装配置到自动构建、测试和部署的全流程。文中还提供了一个 Jenkinsfile 示例,并分享了实践经验,强调了版本控制、自动化测试等关键点的重要性。
36 3
|
9天前
|
存储 Java 关系型数据库
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践,包括连接创建、分配、复用和释放等操作,并通过电商应用实例展示了如何选择合适的连接池库(如HikariCP)和配置参数,实现高效、稳定的数据库连接管理。
24 2
|
10天前
|
Java 关系型数据库 数据库
面向对象设计原则在Java中的实现与案例分析
【10月更文挑战第25天】本文通过Java语言的具体实现和案例分析,详细介绍了面向对象设计的五大核心原则:单一职责原则、开闭原则、里氏替换原则、接口隔离原则和依赖倒置原则。这些原则帮助开发者构建更加灵活、可维护和可扩展的系统,不仅适用于Java,也适用于其他面向对象编程语言。
10 2
|
14天前
|
设计模式 Java API
[Java]静态代理与动态代理(基于JDK1.8)
本文介绍了代理模式及其分类,包括静态代理和动态代理。静态代理分为面向接口和面向继承两种形式,分别通过手动创建代理类实现;动态代理则利用反射技术,在运行时动态创建代理对象,分为JDK动态代理和Cglib动态代理。文中通过具体代码示例详细讲解了各种代理模式的实现方式和应用场景。
14 0
[Java]静态代理与动态代理(基于JDK1.8)
|
15天前
|
安全 Java
Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧
【10月更文挑战第20天】Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧,包括避免在循环外调用wait()、优先使用notifyAll()、确保线程安全及处理InterruptedException等,帮助读者更好地掌握这些方法的应用。
13 1
|
28天前
|
Java 数据库
案例一:去掉数据库某列中的所有英文,利用java正则表达式去做,核心:去掉字符串中的英文
这篇文章介绍了如何使用Java正则表达式从数据库某列中去除所有英文字符。
39 15
|
27天前
|
jenkins Java 测试技术
如何使用 Jenkins 自动发布 Java 代码,通过一个电商公司后端服务的实际案例详细说明
【10月更文挑战第8天】本文介绍了如何使用 Jenkins 自动发布 Java 代码,通过一个电商公司后端服务的实际案例,详细说明了从 Jenkins 安装配置到自动构建、测试和部署的全流程。文中还提供了一个 Jenkinsfile 示例,并分享了实践经验,强调了版本控制、自动化测试等关键点的重要性。
32 5
|
26天前
|
Java
让星星⭐月亮告诉你,jdk1.8 Java函数式编程示例:Lambda函数/方法引用/4种内建函数式接口(功能性-/消费型/供给型/断言型)
本示例展示了Java中函数式接口的使用,包括自定义和内置的函数式接口。通过方法引用,实现对字符串操作如转换大写、数值转换等,并演示了Function、Consumer、Supplier及Predicate四种主要内置函数式接口的应用。
20 1
|
27天前
|
Java
Java基础之 JDK8 HashMap 源码分析(中间写出与JDK7的区别)
这篇文章详细分析了Java中HashMap的源码,包括JDK8与JDK7的区别、构造函数、put和get方法的实现,以及位运算法的应用,并讨论了JDK8中的优化,如链表转红黑树的阈值和扩容机制。
20 1
下一篇
无影云桌面