Java 中序列化流的学习

序列化和反序列化的概述

Java提供了一种对象序列化的机制。用一个字节序列可以表示一个对象,该字节序列包含该对象的数据、对象的类型和对象中存储的属性等信息。字节序列写出到文件之后,相当于文件中持久保存了ー个对象的信息。

反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化。对象的数据、对象的类型和对象中存的数据信息,都可以用来在内存中创建对象。

ObjectOutputStream类

java.io.ObjectOutputStream类,将Java对象的原始数据类型写出到文件,实现对象的持久存储。

构造方法

ObjectOutputStream(OutputStream out)
// 创建写入指定OutputStream的ObjectOutputStream。
参数:
OutputStream out:字节输出流

特有的成员方法

public final void writeObject(Object obj)
// 将指定的对象写入ObjectOutputStream。

举例:使用ObjectOutputStream类,将Java对象的原始数据类型写出到文件。

使用步骤:

1.创建ObjectOutputStream对象,构造方法中传递字节输出流。
2.使用ObjectOutputStream对象中的方法writeObject,把对象写入到文件中。
3.释放资源。

定义一个类,存储这个类对象:

在定义之前,需要知道:

类通过实现java.io.Serializable接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。可序列化类的所有子类型本身都是可序列化的。序列化接口没有方法或字段,仅用于标识可序列化的语义。没有实现这个接口,进行序列化或反序列化,会抛出NotSerializableException异常。

因此,我们需要将Java对象的原始数据写出到文件,那么该对象类必须实现Serializable接口。

import java.io.Serializable;

public class Person implements Serializable {

    private String name;

    private int age;

    public Person() {
    }

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

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", 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;
    }
}

代码实现:

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

public class Demo01ObjectOutputStream {
    public static void main(String[] args) throws IOException {
        method();
    }

    private static void method() throws IOException {
        // 创建ObjectOutputStream对象,构造方法中传递字节输出流。
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("/Users/liyihua/IdeaProjects/Study/src/view/study/demo36/person.txt"));

        // 使用ObjectOutputStream对象中的方法writeObject,把对象写入到文件中。
        oos.writeObject(new Person("程序猿", 22));
        oos.writeObject(new Person("攻城狮", 25));

        // 释放资源。
        oos.close();
    }
}

运行后,生成一个person.txt文件,文件内容如下:

img

里面存储了Person对象的数据信息。

ObjectInputStream类

ObjectInputStream反序列化流,将之前使用ObjectOutputStream序列化的原始数据恢复为对象。

构造方法

ObjectInputStream(InputStream in)
// 创建从指定的InputStream读取的ObjectInputStream。
参数:
InputStream in:字节输入流

特有成员方法

Object readObject()
// 从ObjectInputStream读取对象。

举例:使用ObjectInputStream,将上例中的person.txt文件里,被序列化的Person对象数据信息,反序列化为Person对象。

反序列化的前提:

  1. 类必须实现Serializable接口。
  2. 必须存在对应类的class文件(如本例子中的Person类),如果不存在对应的class文件会抛出ClassNotFoundException异常。

使用步骤:

1.创建ObjectInputStream对象,构造方法中传递字节输入流。
2.使用ObjectInputStream对象中的方法readObject,读取保存对象的文件。
3.释放资源。
4.使用读取出来的对象(打印)。
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class Demo01ObjectInputStream {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        method();
    }

    private static void method() throws IOException, ClassNotFoundException {
        // 创建ObjectInputStream对象,构造方法中传递字节输入流。
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("/Users/liyihua/IdeaProjects/Study/src/view/study/demo36/person.txt"));

        // 使用ObjectInputStream对象中的方法readObject,读取保存对象的文件。
        Object object1 = ois.readObject();
        Object object2 = ois.readObject();

        // 释放资源。
        ois.close();

        // 使用读取出来的对象(打印)。
        Person person1 = (Person) object1;
        Person person2 = (Person) object2;
        System.out.println(person1);
        System.out.println(person2);
    }
}

控制台输出:

Person{name='程序猿', age=22}
Person{name='攻城狮', age=25}

transient关键字

序列化操作

一个对象要想序列化,必须满足两个条件:

  1. 该类必须实现java.io.Serializable接口,Serializable是一个标记接口,不实现此接口的类将不会使任可状态序列化或反序列化,会抛出NotSerializableException。
  2. 该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用transient关键字修饰。

说到transient关键字,首先说一下static关键字(静态关键字)。静态优先与非静态加载到内存中,即会静态优先于对象加载到内存中,被修饰的静态成员变量是不能被序列化的,序列化的都是对象。

transient关键字:瞬态关键字,被transient关键字修饰的成员变量,不能被序列化。

举例

还是上面的两个例子:

不使用static关键字或transient关键字修饰成员变量,结果如上两个例子一样。

假如使用transient关键字修饰age成员变量,其余不更改:

public class Person implements Serializable {

    private String name;

    private transient int age;

    public Person() {
    }

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

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", 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;
    }
}

那么第一个例子中的person.txt文件内容是这样的:

img

第二个例子,控制台的输出:

Person{name='程序猿', age=0}
Person{name='攻城狮', age=0}

把transient关键字换成static关键字,效果也是一样的。

InvalidClassException异常及原理

假如序列化后,要进行反序列化之前,修改了对应Java对象的类,进行反序列化会抛出java.io.InvalidClassException异常。并且告诉我们,有序列号发生冲突。

如上例中,进行序列化后,把Person类的age修饰符从public修改为private,这个时候进行反序列化,会抛出java.io.InvalidClassException异常,并且还会告诉我们:流类序列号和本地类序列号冲突,即ObjectInputStream类序列号和Person类序列号冲突。

原理

如上面的例子:

编译器会把Person.java文件编译生成Person.class文件。又Person类又实现了Serializable接口,这时就会根据Person类的定义,给Person.class文件添加一个序列号(serialVersionUID)。每次修改类的定义,序列号都会发生改变。

进行了序列化后,再进行反序列化的时候,会使用Person.class文件的序列号和Person.txt文件的序列号对比。如果序列号一样,那么反序列化成功,否则反序列化失败,失败后,抛出java.io.InvalidClassException异常。

每次修改类的定义,都会生成新的序列号。这时候,我们可以自行给类添加一个序列号,之后,无论是否对类的定义进行修改,都不会重新生成新的序列号。

如在上例中的Person类中添加:

private static final long serialVersionUID = 1L;

这时候,无论是否对Person类的定义进行修改,序列号都是1L。

自行添加序列号格式:

[修饰符] static final long serialVersionUID = 自定义序列号值;

练习:序列化集合

当我们想要在文件中保存多个对象的时候,可以把对象存储到一个集合中,然后对集合进行序列化和反序列化。

  1. 将存有多个自定义对象的集合序列化操作,保存到list.txt文件中。
  2. 反序列化list.txt,遍历集合,打印对象信息。

创建一个Person类

import java.io.Serializable;

public class Person implements Serializable {

    private String name;

    private int age;

    public Person() {
    }

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

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", 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;
    }
}

将存有多个自定义对象的集合序列化操作,保存到list.txt文件中:

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.ArrayList;

public class DemoArrayPersonSerializable {
    public static void main(String[] args) throws IOException {
        method();
    }

    private static void method() throws IOException {
        // 创建ObjectOutputStream对象,构造方法中传递字节输出流。
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("/Users/liyihua/IdeaProjects/Study/src/view/study/demo36/list.txt"));

        // 创建ArrayList集合对象
        ArrayList<Person> personArrayList = new ArrayList<>();

        // 往集合中添加多个Person对象
        personArrayList.add(new Person("程序猿", 22));
        personArrayList.add(new Person("攻城狮", 25));
        personArrayList.add(new Person("骇客", 19));

        // 使用ObjectOutputStream对象中的方法writeObject,把ArrayList集合对象写入到文件中。
        oos.writeObject(personArrayList);

        // 释放资源。
        oos.close();
    }
}

生成list.txt文件,文件内容如下:

��srjava.util.ArrayListx����a�Isizexpwsrview.study.demo36.Person*Xs���pIageLnametLjava/lang/String;xpt    程序猿sq~t    攻城狮sq~t骇客x

反序列化list.txt,遍历集合,打印对象信息:

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.ArrayList;

public class DemoArrayObjectInputStream {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        method();
    }

    private static void method() throws IOException, ClassNotFoundException {
        // 创建ObjectInputStream对象,构造方法中传递字节输入流。
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("/Users/liyihua/IdeaProjects/Study/src/view/study/demo36/list.txt"));

        // 使用ObjectInputStream对象中的方法readObject,读取保存对象的文件。
        Object object = ois.readObject();

        // 释放资源。
        ois.close();

        // 使用读取出来的对象(打印)。
        ArrayList<Person> personArrayList = (ArrayList) object;
        for (Person person : personArrayList) {
            System.out.println(person);
        }
    }
}

控制台输出:

Person{name='程序猿', age=22}
Person{name='攻城狮', age=25}
Person{name='骇客', age=19}
posted @ 2020-02-07 05:08  LeeHua  阅读(553)  评论(0编辑  收藏  举报