Java 对象序列化和反序列化详解

本文主要是整合了一下两篇博客的内容,第一篇引用其概念,第二篇引用了关于序列化ID的介绍,并结合自己的例子进行解释。
https://www.cnblogs.com/xdp-gacl/p/3777987.html
https://www.cnblogs.com/lukelook/p/11192677.html

一、序列化和反序列化的概念

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

  对象的序列化主要有两种用途:
  1) 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;
  2) 在网络上传送对象的字节序列。

在很多应用中,需要对某些对象进行序列化,让它们离开内存空间,入住物理硬盘,以便长期保存。比如最常见的是Web服务器中的Session对象,当有 10万用户并发访问,就有可能出现10万个Session对象,内存可能吃不消,于是Web容器就会把一些seesion先序列化到硬盘中,等要用了,再把保存在硬盘中的对象还原到内存中。

当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象。

二、JDK类库中的序列化API

java.io.ObjectOutputStream代表对象输出流,它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。

java.io.ObjectInputStream代表对象输入流,它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。
  
  只有实现了Serializable和Externalizable接口的类的对象才能被序列化。Externalizable接口继承自 Serializable接口,实现Externalizable接口的类完全由自身来控制序列化的行为,而仅实现Serializable接口的类可以 采用默认的序列化方式 。
  对象序列化包括如下步骤:
  1) 创建一个对象输出流,它可以包装一个其他类型的目标输出流,如文件输出流(FileOutputStream);
  2) 通过对象输出流的writeObject()方法写对象。

对象反序列化的步骤如下:
  1) 创建一个对象输入流,它可以包装一个其他类型的源输入流,如文件输入流;
  2) 通过对象输入流的readObject()方法读取对象。

举例
对象A实现了Serializable接口

class  A implements Serializable {
    private String name;
    private int age;

    public A(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "A{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

A对象序列化和反序列化

public class No1 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        A a=new  A("zph",21);
        ObjectOutputStream objectOutputStream=new ObjectOutputStream(new FileOutputStream("testIO.txt"));
        objectOutputStream.writeObject(a);
        objectOutputStream.close();

        ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream("testIO.txt"));
        A a1= (A) objectInputStream.readObject();
        System.out.println(a1);
        //str=str.replace(0,str.length()," ","");
    }
}

序列化结果:
testIO.txt中存储了如下的内容,
在这里插入图片描述
反序列化结果:
Java 对象序列化和反序列化详解_第1张图片

三、serialVersionUID的作用

序列化ID的作用:

java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地实体类中的serialVersionUID进行比较,如果相同则认为是一致的,便可以进行反序列化,否则就会报序列化版本不一致的异常。

序列化ID如何产生:

当我们一个实体类中没有显示的定义一个名为“serialVersionUID”、类型为long的变量时,Java序列化机制会根据编译时的class自动生成一个serialVersionUID作为序列化版本比较,这种情况下,只有同一次编译生成的class才会生成相同的serialVersionUID。如果我们后续修改了这个类(如新增了变量,或者修改了函数),那么编译过后会生成新的serialVersionUID,这时和原先序列化后的serialVersionUID不一致,导致反序列化失败。为了不让这个序列化ID每次编译都进行更改,可以显示的声明这个变量,如

private  static  final  long serialVersionUID = -1739077855220753166L;

JVM是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化 ID 是否一致(就是 private static final long serialVersionUID = 1L)。

举例:
按照上述的序列化以后在testIO.txt中存储了A对象的信息,现在对A对象修改,比如去掉无参的构造函数,然后仅仅执行反序列化。

public class No1 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream("testIO.txt"));
        A a1= (A) objectInputStream.readObject();
        System.out.println(a1);
    }
}

会报如下的错误:

Exception in thread “main” java.io.InvalidClassException: com.Easy.A;
local class incompatible: stream classdesc serialVersionUID =
7619878586498448713, local class serialVersionUID = 508988896769428968

所以尽量都要在序列化的对象中显示的申明serialVersionUID。

四、子类与父类序列化相关问题

  • 如果父类实现了序列化,那么子类就不需要在实现序列化了,默认就会序列化
  • 如果父类没有实现序列化,子类实现了,那么只能序列化子类中的属性,父类的属性会被跳过

那么如果不让父类实现序列化,但是也要序列化父类的属性该怎么办呢?

“To allow subtypes of non-serializable classes to be serialized, the subtype may assume responsibility for saving and restoring the state of the supertype’s public, protected, and (if accessible) package fields. The subtype may assume this responsibility only if the class it extends has an accessible no-arg constructor to initialize the class’s state. It is an error to declare a class Serializable if this is not the case. The error will be detected at runtime. ”

1.子类需要序列化(反序列化)父类可访问的域
2.需要创建父类的无参构造函数

例子可以参考java序列化和反序列化使用总结

你可能感兴趣的:(Java学习)