JAVA序列化与反序列

619 阅读5分钟

1.什么是序列化和反序列化

序列化

把对象转换为字节序列(即字节流)的过程称为对象的序列化。

反序列化

把字节序列(即字节流)恢复为对象的过程称为对象的反序列化。

2.什么情况下需要序列化和反序列化

存在跨进程传输对象都需要序列化、反序列化。以下场景都需要序列化和反序列化,例如将内存中的对象持久化到磁盘、数据库中时; 浏览器与服务端进行数据交互时;PRC分布式框架下数据交互(dubbo、mq)。

可能会有同学有如下疑问:

1.通常java web项目,浏览器与服务端数据交互时,我们都会定义一个DTO对象,并且这个对象也不会实现java.io.Serializable接口,但是浏览器能和服务端正常通信,这是为什么呢?

原因:浏览器与服务端进行数据通信时,双方会把对象转换为JSON字符串,因此浏览器与服务器交互时的数据格式其实是字符串。来看一下String的源码,String实现了Serializable接口, 并显示指定serialVersionUID的值。

public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];

    /** Cache the hash code for the string */
    private int hash; // Default to 0

    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    private static final long serialVersionUID = -6849794470754667710L;

2.当我们要对象持久化到数据库中时,对象也不会去实现java.io.Serializable接口,这是为什么呢?

原因:我们并不是将整个对象持久化到数据库中, 而是将对象中的属性持久化到数据库中, 而这些属性都是实现了Serializable接口的基本属性。比如说Integer类继承了Number类,Number类实现了java.io.Serializable接口,并显示指定serialVersionUID的值。

public abstract class Number implements java.io.Serializable {
    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    private static final long serialVersionUID = -8742448824652078965L;
}

3.实现序列化和反序列化为什么要实现Serializable接口?

下面看一下java.io.Serializable接口的源码,Serializable是一个空接口,没有什么具体内容,它的目的只是简单的标识一个类的对象可以被序列化。该接口的具体实现类由底层JVM提供。

public interface Serializable {
}

4.实现Serializable接口的同时为什么还要显示指定serialVersionUID的值?必要嘛?

serialVersionUID是为了保证序列化和反序列化的对象一致。一个比较常见的场景就是分布式的情况下,比如说dubbo,provider方提供api的jar包版本是1.0.0,其中某个序列化对象(记为A)有两个属性name,age,但是没有显示指定serialVersionUID的值,consumer也使用1.0.0的api包。过了一段时间后,provider方提供api的jar包版本升级到了2.0.0,同时A对象增加了一个属性sex,那么consumer方继续使用1.0.0的api包,必然导致异常。验证如下。

1.创建一个需要序列化对象,但是不显示指定serialVersionUID

@Data
public class PersonDTO implements Serializable {
    private String name;

    private String age;
}

2.创建一个序列化与反序列化的工具类

import java.io.*;

/**
 * @author zinsanity
 * @date 2020-05-28 17:57
 * @desc
 */
public class SerializeUtil {

    public static <T> void serialize(T t, String path) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File(path)));
        oos.writeObject(t);
        oos.close();
    }

    public static Object deserialize(String path) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File(path)));
        Object object = ois.readObject();
        ois.close();
        return object;
    }
}

3.序列化PersonDTO实例,保存到硬盘中

public class SerializeTest {

    @Test
    public void test() throws IOException {
        PersonDTO personDTO = new PersonDTO();
        personDTO.setAge("18");
        personDTO.setName("tony");
        System.out.println("####序列化前:"+ JSON.toJSONString(personDTO));
        // 注意win系统路径的表示方式
        SerializeUtil.serialize(personDTO, "D://test_seralize.txt");
    }
}

4.PersonDTO类增加一个属性sex

    private String name;

    private String age;

    private String sex;

5.反序列化

    @Test
    public void test1() throws IOException, ClassNotFoundException {
        PersonDTO personDTO = (PersonDTO)SerializeUtil.deserialize("D://test_seralize.txt");
        System.out.println("####反序列化后:"+ JSON.toJSONString(personDTO));
    }

6.异常如下

java.io.InvalidClassException: com.example.demo.dto.PersonDTO; local class
incompatible: stream classdesc serialVersionUID = -4255753523043399861, local
class serialVersionUID = -8745272773692115845

结论

如果不显示指定serialVersionUID, JVM在序列化时会根据属性自动生成一个serialVersionUID, 然后与属性一起序列化, 再进行持久化或网络传输. 在反序列化时, JVM会再根据属性自动生成一个新版serialVersionUID, 然后将这个新版serialVersionUID与序列化时生成的旧版serialVersionUID进行比较, 如果相同则反序列化成功, 否则报错。反序列化时,如果与序列化时的对象属性不一致(包括数量不一致,属性名不一致)都会导致反序列化时创建的serialVersionUID与序列化不同。

Java序列化的其他特性参考博客

1.被transient关键字修饰的属性不会被序列化

2.static关键字的属性也不会被序列化。

因为序列化是针对对象而言的, 而static属性优先于对象存在, 随着类的加载而加载, 所以不会被序列化.

看到这个结论, 是不是有人会问, serialVersionUID也被static修饰, 为什么serialVersionUID会被序列化? 其实serialVersionUID属性并没有被序列化, JVM在序列化对象时会自动生成一个serialVersionUID, 然后将我们显示指定的serialVersionUID属性值赋给自动生成的serialVersionUID。