Java序列化与反序列化详解

  • 1. 序列化与反序列化概述
  • 1.1 序列化与反序列化简介
  • 1.2 序列化与反序列化作用
  • 1.3 序列化与反序列化工具
  • 2. 序列化与反序列化工具
  • 2.1 serialVersionUID
  • 2.2 fastjson
  • 2.3 Kryo
  • 2.4 jackson
  • 2.5 gson
  • 3. 实现验证


1. 序列化与反序列化概述

1.1 序列化与反序列化简介

序列化:把对象转换为字节序列的过程称为对象的序列化。
反序列化:把字节序列恢复为对象的过程称为对象的反序列化。

1.2 序列化与反序列化作用

序列化与反序列化的作用就是将内存对象持久化存储或者对象通过网络传输到远端。就是为了对象的存储与传输,对象的系列化更方便对象的存储与传输,另外序列化与反序列化不仅限于Java,这里主要为了说明Java中的序列化与反序列化。

1.3 序列化与反序列化工具

目前JAVA常用的序列化有protobuf,json,xml,Serializable,hessian,kryo。
Serializable:JDK 原生是 Java 自带的序列化框架。

protobuf:谷歌公司出的一款开源项目,转码性能高,支持多语言;

JSON:用途最广泛,序列化方式还衍生了阿里的fastjson,谷歌的GSON,jackson,美团的MSON等更加优秀的转码工具。现在越来越多的网站采用JSON来交换数据,在Json.org网站上,Java可以使用的解析Json的组件就有超过20种。
Json官网:http://json.org/

Kryo 是一个快速高效的二进制序列化框架,号称是 Java 领域最快的。它的特点是序列化速度快、体积小、接口易使用。

2. 序列化与反序列化工具

2.1 serialVersionUID

1.Serializable概念
JDK 原生是 Java 自带的序列化框架,与 Java 语言是强绑定的,通过 JDK 将对象序列化后是无法通过其他语言进行返序列化的,所以它的通用性比较差。Serializable仅仅是一个标识的作用,用来告诉 JVM 这个对象可以被序列化。

2.serialVersionUID的作用
序列化时为了保持版本的兼容性,即在版本升级时反序列化仍保持对象的唯一性。

3.serialVersionUID两种生成方式
一个是默认的1L,比如:private static final long serialVersionUID = 1L;
一个是根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段,比如:

public class ClueResult implements Serializable {
private static final long serialVersionUID = 8196133923301996629L;
private static final long serialVersionUID = 1L;
}

4.serialVersionUID两种生成方式区别
看上去,好像每个类的这个类不同,似乎这个SerialVersionUid在类之间有某种关联。其实不然,两种都可以,从JDK文档也看不出这一点。我们只要保证在同一个类中,不同版本根据兼容需要,是否更改SerialVersionUid即可。

对于第一种serialVersionUID = 1L,需要了解哪些情况是可兼容的,哪些根本就不兼容。 在可兼容的前提下,可以保留旧版本号,如果不兼容,或者想让它不兼容,就手工递增版本号。1->2->3…

第二种方式,是根据类的结构产生的hash值。增减一个属性、方法等,都可能导致这个值产生变化。我想这种方式适用于这样的场景,开发者认为每次修改类后就需要生成新的版本号,不想向下兼容,操作就是删除原有serialVesionUid声明语句,再自动生成一下。

2.2 fastjson

fastjson 是阿里巴巴出品的一款序列化框架,可以将对象序列化为 JSON 字符串,已性能著称,但是性能是以写死代码,一味追求性能忽略代码质量为代价,截止到目前Issues为1.7K,问题很多,使用确实方便。
fastjson github地址:https://github.com/alibaba/fastjson/

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.76</version>
</dependency>

2.3 Kryo

Kryo 是一个快速高效的二进制序列化框架,号称是 Java 领域最快的。它的特点是序列化速度快、体积小、接口易使用。
Kryo支持自动深/浅拷贝,它是直接通过对象->对象的深度拷贝,而不是对象->字节->对象的过程。
关于 Kryo 更多的介绍可以去 Github 查看:https://github.com/EsotericSoftware/kryo
关于通用性,Kryo 是一款针对 Java 语言开发的框架,基本很难跨语言使用,因此通用性比较差。

<dependency>
    <groupId>com.esotericsoftware</groupId>
    <artifactId>kryo</artifactId>
    <version>5.3.0</version>
</dependency>

2.4 jackson

非常有名的json解析工具jackson。

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.9.2</version>
</dependency>
 
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.2</version>
</dependency>
 
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-annotations</artifactId>
    <version>2.9.2</version>
</dependency>

2.5 gson

谷歌的json工具GSON。

<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.8.2</version>
</dependency>

3. 实现验证

UserDTO

package com.jerry.unit.json;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

/**
 * @author zrj
 * @since 2022/3/24
 **/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserDTO implements Serializable {
    private static final long serialVersionUID = 8196133923301996629L;
    private String name;
    private String age;
    private String phone;
}

JDKSerializationUtil

package com.jerry.unit.json;

import java.io.*;

/**
 * JDK原生序列化Serializable
 *
 * @author zrj
 * @since 2022/3/24
 **/
public class JDKSerializationUtil {
    /**
     * 序列化
     *
     * @param obj 待序列化对象
     * @return 二进制字节数组
     * @throws IOException
     */
    public static byte[] serialize(Object obj) throws IOException {
        // 字节输出流
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        // 将对象序列化为二进制字节流
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(obj);
        // 获取二进制字节数组
        byte[] bytes = byteArrayOutputStream.toByteArray();
        //  关闭流
        objectOutputStream.close();
        byteArrayOutputStream.close();
        return bytes;
    }

    /**
     * 反序列化
     *
     * @param bytes 待反序列化二进制字节数组
     * @param <T>   反序列对象类型
     * @return 反序列对象
     * @throws IOException
     * @throws ClassNotFoundException
     */
    public static <T> T deSerialize(byte[] bytes) throws IOException, ClassNotFoundException {
        // 字节输入流
        final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
        // 将二进制字节流反序列化为对象
        final ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
        final T object = (T) objectInputStream.readObject();
        // 关闭流
        objectInputStream.close();
        byteArrayInputStream.close();
        return object;
    }
}

KryoSerializationUtil

package com.jerry.unit.json;

import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;

/**
 * @author zrj
 * @since 2022/3/24
 **/
public class KryoSerializationUtil {
    /**
     * 序列化
     *
     * @param obj  待序列化对象
     * @param kryo kryo 对象
     * @return 字节数组
     */
    public static byte[] serialize(Object obj, Kryo kryo) {
        Output output = new Output(1024);
        kryo.writeObject(output, obj);
        output.flush();
        return output.toBytes();
    }

    /**
     * 反序列化
     *
     * @param bytes 待反序列化二进制字节数组
     * @param <T>   反序列对象类型
     * @return 反序列对象
     */
    public static <T> T deSerialize(byte[] bytes, Class<T> clazz, Kryo kryo) {
        Input input = new Input(bytes);
        return kryo.readObject(input, clazz);
    }
}

SerializationUtils

package com.jerry.unit.json;

import com.alibaba.fastjson.JSON;
import com.esotericsoftware.kryo.Kryo;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import org.junit.Test;

import java.io.IOException;
import java.util.Arrays;

/**
 * @author zrj
 * @since 2022/3/24
 **/
public class SerializationUtils {
    /**
     * JDK原生序列化Serializable
     */
    @Test
    public void jDKSerializationTest() throws IOException, ClassNotFoundException {
        System.out.println("JDK原生序列化Serializable");
        UserDTO userDTO = new UserDTO("jerry", "12", "123");
        byte[] bytes = JDKSerializationUtil.serialize(userDTO);
        System.out.println("【JDK原生序列化Serializable】序列化成功:" + Arrays.toString(bytes));
        System.out.println("【JDK原生序列化Serializable】byte size=" + bytes.length);

        UserDTO userDTO1 = JDKSerializationUtil.deSerialize(bytes);
        System.out.println("【JDK原生序列化Serializable】反序列化成功:" + userDTO1);
    }

    /**
     * fastJson序列化
     */
    @Test
    public void fastJsonSerializationTest() {
        System.out.println("fastJson序列化");
        UserDTO userDTO = new UserDTO("jerry", "12", "123");
        //将一个对象序列化为 json 字符串:
        String userJsonString = JSON.toJSONString(userDTO);
        System.out.println("【fastJson】序列化成功,userJsonString:" + userJsonString);

        UserDTO userDTO2 = JSON.parseObject(userJsonString, UserDTO.class);
        System.out.println("【fastJson】反序列化成功,userDTO2:" + userDTO2);
    }

    /**
     * Kryo序列化
     */
    @Test
    public void kryoSerializationTest() {
        System.out.println("Kryo序列化");
        UserDTO userDTO = new UserDTO("jerry", "12", "123");

        Kryo kryo = new Kryo();
        kryo.setRegistrationRequired(false);
        // kryo.register(UserDTO.class);
        byte[] kryoBytes = KryoSerializationUtil.serialize(userDTO, kryo);
        System.out.println("【Kryo】序列化成功:" + Arrays.toString(kryoBytes));
        System.out.println("【Kryo】byte size=" + kryoBytes.length);

        UserDTO userDTO3 = KryoSerializationUtil.deSerialize(kryoBytes, UserDTO.class, kryo);
        System.out.println("【Kryo】反序列化成功:" + userDTO3);
    }

    /**
     * Jackson序列化
     */
    @Test
    public void jacksonSerializationTest() throws JsonProcessingException {
        System.out.println("Jackson序列化");
        UserDTO userDTO = new UserDTO("jerry", "12", "123");
        /**
         * ObjectMapper是JSON操作的核心,Jackson的所有JSON操作都是在ObjectMapper中实现。
         * ObjectMapper有多个JSON序列化的方法,可以把JSON字符串保存File、OutputStream等不同的介质中。
         * writeValue(File arg0, Object arg1)把arg1转成json序列,并保存到arg0文件中。
         * writeValue(OutputStream arg0, Object arg1)把arg1转成json序列,并保存到arg0输出流中。
         * writeValueAsBytes(Object arg0)把arg0转成json序列,并把结果输出成字节数组。
         * writeValueAsString(Object arg0)把arg0转成json序列,并把结果输出成字符串。
         */
        ObjectMapper mapper = new ObjectMapper();
        // User类转JSON
        String json = mapper.writeValueAsString(userDTO);
        System.out.println("【Jackson序列化】序列化成功:" + json);

        // JSON解析
        UserDTO userDTO2 = mapper.readValue(json, UserDTO.class);
        System.out.println("【Jackson序列化】反序列化成功:" + userDTO2);
    }

    /**
     * gson序列化
     */
    @Test
    public void gsonSerializationTest() throws JsonProcessingException {
        System.out.println("gson序列化");
        UserDTO userDTO = new UserDTO("jerry", "12", "123");

        // User类转JSON
        Gson gson = new Gson();
        String json = gson.toJson(userDTO);
        System.out.println("【gson序列化】序列化成功:" + json);

        // JSON解析
        UserDTO userDTO2 = gson.fromJson(json, UserDTO.class);
        System.out.println("【Jackson序列化】反序列化成功:" + userDTO2);
    }

}