Java 序列化

一、序列化的意义:

序列化:将 Java 中的实例从内存写到文件中。

反序列化:将文件中的对象实例写到内存中。

意义:序列化机制允许将实现序列化的 Java 对象转换位字节序列,这些字节序列可以保存在磁盘上,或通过网络传输,以达到以后恢复成原来的对象。序列化机制使得对象可以脱离程序的运行而独立存在。

使用场景:所有可在网络上传输的对象都必须是可序列化的,比如 RMI(remote method invoke, 即远程方法调用),传入的参数或返回的对象都是可序列化的,否则会出错;所有需要保存到磁盘的 java 对象都必须是可序列化的。通常建议:程序创建的每个 JavaBean 类都实现 Serializeable 接口。

Java 提供了对象序列化的机制,该机制可以将一个对象表示为一个字节序列,该字节序列包括该对象的数据、有关对象的类型的信息和存储在对象中数据的类型。将序列化对象写入文件之后,可以从文件中读取出来,并且对它进行反序列化,也就是说,对象的类型信息、对象的数据,还有对象中的数据类型可以用来在内存中新建对象。整个过程都是 Java 虚拟机(JVM)独立的,也就是说,在一个平台上序列化的对象可以在另一个完全不同的平台上反序列化该对象。类 ObjectInputStream 和 ObjectOutputStream 是高层次的数据流,它们包含反序列化和序列化对象的方法。ObjectInputStream 的 writeObject 方法序列化一个对象,并将它发送到输出流。

public final void writeObject​(Object obj) throws IOException

 ObjectInputStream 类包含如下反序列化一个对象的方法:

public final Object readObject() throws IOException, ClassNotFoundException

该方法从流中取出下一个对象,并将对象反序列化。它的返回值为 Object,因此,你需要将它转换成合适的数据类型。

序列化机制允许将实现序列化的 Java 对象转换位这些字节可以保存在硬盘上或通过网络传输,最终恢复成原来的独享。序列化使得对象可以脱离程序的运行而独立存在。所有可在网络上传输的对象都必须是可序列化的,比如 RMI(remote method invoke),传入的参数或返回的对象都是可序列化的,否则会出错;所有需要保存到磁盘的 java 对象都必须是实现 Serializeable 接口可序列化的。

 

二、序列化实现

如果需要将某个对象保存到磁盘上或者通过网络传输,那么这个类应该实现 Serializeable 接口或者 Externalizable 接口

  1. Serializeable 接口是个标记接口,类继承此接口后, 此类的实例都可以序列化。
  2. 创建一个 ObjectOutputStream 输出流;
  3. 调用 ObjectOutputStream 对象的 writeObject 输出可序列化对象。

可序列化对象 Person

package serializable;

import java.io.Serializable;

/**************************************************************************************************
 @Copyright 2003-2022
 @package serializable
 @date 2022/2/19 18:23
 @author qiao wei
 @version 1.0
 @brief
 @history
 *************************************************************************************************/
public class Person implements Serializable {
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
    
    private String name;
    private int age;
}

 序列化对象,保存到 object.txt 中

package serializable;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

/**************************************************************************************************
 @Copyright 2003-2022
 @package serializable
 @date 2022/2/19 18:25
 @author qiao wei
 @version 1.0
 @brief
 @history
 *************************************************************************************************/
public class WriteClient {

    public static void main(String[] args) {
        writePerson();
    }
    
    public static void writePerson() {
        try {
            ObjectOutputStream objectOutputStream =
                    new ObjectOutputStream(
                            new FileOutputStream("src/serializable/object.txt"));
            Person person = new Person("九哥", 42);
            objectOutputStream.writeObject(person);

        } catch (Exception exception) {
            exception.printStackTrace();
        }
    }
}

 

打开 object.txt 文件可以看到二进制格式。通过以上代码可将一个 Java 对象以 “字节流” 的方式保存到文件中,因为保存的内容全部是二进制的,所以出现了上面这种乱码的情况。保存的文件本身是不可以直接修改的,因为会破坏其保存的格式。对象的 “序列化” 并没有保存对象所有的内容,而仅仅保留了对象的属性,没有保留对象的方法,之所以这么做的原因是同一类对象中每个对象都具备相同的方法,但是每个对象的属性却不一定相同,所以我们序列化时只要保存对象的属性就可以了。

如果一个可序列化的类的成员有引用类型,那这个引用类型也必须是可序列化的;否则,会导致此类不能序列化。

有引用类型字段的 Teacher 类

package serializable;

import java.io.Serializable;

/**************************************************************************************************
 @Copyright 2003-2022
 @package serializable
 @date 2022/2/19 19:05
 @author qiao wei
 @version 1.0
 @brief
 @history
 *************************************************************************************************/
public class Teacher implements Serializable {
    
    public Teacher(String name, Person person) {
        this.name = name;
        this.person = person;
    }
    
    private String name;
    private Person person;
}

序列化对象到 teacher.txt

package serializable;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

/**************************************************************************************************
 @Copyright 2003-2022
 @package serializable
 @date 2022/2/19 18:25
 @author qiao wei
 @version 1.0
 @brief
 @history
 *************************************************************************************************/
public class WriteClient {

    public static void main(String[] args) {
        writeTeacher();
    }
    
    public static void writeTeacher() {
        try {
            ObjectOutputStream objectOutputStream =
                    new ObjectOutputStream(
                            new FileOutputStream("src/serializable/teacher.txt"));
            
            Teacher teacher = new Teacher("王老师", new Person("ww", 24));
            objectOutputStream.writeObject(teacher);
        } catch (FileNotFoundException exception) {
            exception.printStackTrace();
        } catch (IOException exception) {
            exception.printStackTrace();
        }
    }
}

三、反序列化实现

  1. 创建一个 ObjectInputStream 输入流;
  2. 调用 ObjectInputStream 对象的 readObject () 得到序列化的对象。
package serializable;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;

/**************************************************************************************************
 @Copyright 2003-2022
 @package serializable
 @date 2022/2/19 18:32
 @author qiao wei
 @version 1.0
 @brief
 @history
 *************************************************************************************************/
public class ReadClient {

    public static void main(String[] args) {
        readPerson();
//        readTeacher();
    }
    
    private static void readPerson() {
        try {
            ObjectInputStream objectInputStream =
                    new ObjectInputStream(
                            new FileInputStream("src/serializable/object.txt"));
            
            Person person = (Person) objectInputStream.readObject();
            System.out.println(person);
        } catch (Exception exception) {
            exception.printStackTrace();
        }
    }
    
    private static void readTeacher() {
        try {
            ObjectInputStream objectInputStream =
                    new ObjectInputStream(
                            new FileInputStream("src/serializable/teacher.txt"));

            Teacher teacher = (Teacher) objectInputStream.readObject();
            System.out.println(teacher);
        } catch (Exception exception) {
            exception.printStackTrace();
        }
    }
}

 

四:对象序列化和反序列化的版本兼容问题

在对象进行序列化或者反序列化的操作时,要考虑 JDK 的版本问题。如果序列化时用的 Class 版本与反序列化时用的 Class 版本不一致的话就有可能造成异常。所以在序列化中引入了一个 serialVersionUID 的常量。


可以通过这个常量来验证序列化版本的一致性。在进行反序列化时,JVM 会把传来的字节流中的 serialVersionUID 与本地的相应的 serialVersionUID 进行一个比较,如果相同就认为是一致的,可以进行反序列化的操作,否则就会发生因为序列化版本不一致的异常。


当实现序列化的类没有显示的定义一个 serialVersionUID 时,Java 序列化机制会在编译时自动生成一个此版本 de 的 serialVersionUID. 当然,也可以自己显示的定义一个 UID,类型为 long 的变量,只要不修改过这个变量值的序列化实体都可以相互序列化和反序列化。

五:序列化的几个注意点

  1. 如果父类已经实现了 Serializable 序列化接口的话,其子类就不用再实现这个接口了,但是反过来就不成立了。
  2. 只有非静态的数据成员才会通过序列化操作被序列化。
  3. 静态(Static)数据成员和被标记了 transient 的数据成员在序列化的过程中将不会被序列化,因此,如果你不想要保存一个非静态的数据成员你就可以给标记上 transient。
  4. 当一个对象被反序列化时,这个对象的构造函数将不会在被调用的。
  5. 需要实现序列化的对象如果内部引用了一个没有实现序列化接口的对象,在其序列化过程中将会发生错误。

 

你可能感兴趣的:(Java,开发语言,Java)