Java基础学习总结:IO之(五)对象流与序列化

一、对象流与序列化

1、简介

对象流,包括对象输入流和对象输出流,流中流动的是对象。

  1. ObjectOutputStream:对象输出流(内存—>硬盘),将一个对象写入到本地文件,被称为对象的序列化。
  2. ObjectInputStream:对象输入流(硬盘—>内存),将一个本地文件中的对象读取出来,被称为对象的反序列化。

注意:

  1. 一个对象流只能操作一个对象,如果试图采用一个对象流操作多个对象的话,会出现EOFException【End Of File:文件意 外达到了文件末尾】
  2. 如果向将多个对象序列化到本地,可以借助于集合,【思路:将多个对象添加到集合中,将集合的对象写入到 本地文件中,再次读出来,获取到的仍然是集合对象,遍历集合】

2、ObjectOutputStream(序列化)

(1)继承关系

java.lang.Object
|____java.io.OutputStream
     |____java.io.ObjectOutputStream

(2)类声明

public class ObjectOutputStream
    extends OutputStream implements ObjectOutput, ObjectStreamConstants
{}

(3)构造方法

    public ObjectOutputStream(OutputStream out) throws IOException {
        verifySubclass();
        bout = new BlockDataOutputStream(out);
        handles = new HandleTable(10, (float) 3.00);
        subs = new ReplaceTable(10, (float) 3.00);
        enableOverride = false;
        writeStreamHeader();
        bout.setBlockDataMode(true);
        if (extendedDebugInfo) {
            debugInfoStack = new DebugTraceInfoStack();
        } else {
            debugInfoStack = null;
        }
    }

    protected ObjectOutputStream() throws IOException, SecurityException {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
        }
        bout = null;
        handles = null;
        subs = null;
        enableOverride = true;
        debugInfoStack = null;
    }

 ObjectOutputStream 有两个构造方法,一个公有的,接受一个OutputStream 类型的参数,另一个构造方法是受保护的无参的构造方法。

(4)简单使用

创建一个Student类,用于序列化:

package basis.StuIO.StuObjectStream;


public class Student{
    private String name;
    private String address;

    public Student(String name, String address) {
        this.name = name;
        this.address = address;
    }
    public Student() {
    }

    //省略getter和setter

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


}

主类(测试类):

package basis.StuIO.StuObjectStream;

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

public class StuObjectStream {
    public static void main(String[] args) throws IOException {
        //创建待序列化的对象
        Student suxing = new Student("苏星","北京");
        //创建用于序列化的对象输出流,将对象写入到项目根目录下的一个二进制文件中
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("stu.bin"));
        oos.writeObject(suxing);
        System.out.println("序列化完成。");
    }
}

运行发现报异常:

Exception in thread "main" java.io.NotSerializableException: basis.StuIO.StuObjectStream.Student

告诉我们,这个对象不能被序列化,原来,序列化不仅仅只使用 ObjectOutputStream 流输出对象这么简单,还要求我们需要序列化对象的类实现 Serializable 接口;

Serializable 接口是一个标记接口,接口中没有任何字段和方法,我们只需要实现即可。标志着这个;类的对象可以被序列化。

修改Student类:

public class Student implements Serializable {}

其他不变,运行程序。

控制台输出 “序列化完成。” 并且,在项目根目录找到名为 “stu.bin” 的二进制文件。标志着序列化成功。

3、ObjectInputStream(反序列化)

(1)继承关系

java.lang.Object
|____java.io.InputStream
     |____ava.io.ObjectInputStream

(2)类声明

public class ObjectInputStream
    extends InputStream implements ObjectInput, ObjectStreamConstants
{}

(3)构造方法

    public ObjectInputStream(InputStream in) throws IOException {
        verifySubclass();
        bin = new BlockDataInputStream(in);
        handles = new HandleTable(10);
        vlist = new ValidationList();
        serialFilter = ObjectInputFilter.Config.getSerialFilter();
        enableOverride = false;
        readStreamHeader();
        bin.setBlockDataMode(true);
    }


    protected ObjectInputStream() throws IOException, SecurityException {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
        }
        bin = null;
        handles = null;
        vlist = null;
        serialFilter = ObjectInputFilter.Config.getSerialFilter();
        enableOverride = true;
    }

 和ObjectOutputStream 一样,ObjectInputStream 也拥有两个构造方法,一个接受InputStream 类型的有参公有构造,一个无参的受保护的构造方法。

(4)反序列化实例

我们对上面序列化得到的二进制文件进行反序列化,并输出结果观察:

package basis.StuIO.StuObjectStream;

import java.io.FileInputStream;
import java.io.ObjectInputStream;

public class StuObjectStream {
    public static void main(String[] args) throws Exception {
        Student student = null;
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("stu.bin"));
        student = (Student) ois.readObject();
        System.out.println(student);
    }
}

输出结果:

Student{name='苏星', address='北京'}

跟我们之前创建的Student对象一致,说明我们反序列化成功了。

4、SerializableUID

我们如上面的示例,先序列化一个对象,得到该对象的二进制文件。

然后,我们修改 Student 类,在类中添加一个属性 age,添加相应的 getter和setter,重写toString() 方法。

然后再对各刚刚得到的二进制文件进行反序列化,会发现反序列化不成功,控制台报 InvalidClassException 异常:

Exception in thread "main" java.io.InvalidClassException:
basis.StuIO.StuObjectStream.Student; local class incompatible: stream classdesc
serialVersionUID = 2809223061937765453, local class serialVersionUID = -2338080562382343211

它告诉我们两个类的 serializableUID 值不一样,JVM告诉我们进行反序列化的对象的类,和我们接收的Student类不是同一个类,所以序列化不成功。

解决:

serializableUID 是一个类的序列化版本,每个类的该值都不相同。为我们的Student类添加该字段后,才能保证对象在序列化之后被修改,然后再进行序列化时,两个对象的序列化版本一致。

在添加age字段之前,为Student类添加 serializableUID :

idea快速添加SerializableUID的方法:https://blog.csdn.net/qq_36090419/article/details/80626489

public class Student implements Serializable {


    private static final long serialVersionUID = 7967100133902769024L;
    private String name;
    private String address;
    //private int age;
}

重复我们第一次对Student 对象的序列化,得到stu.bin文件。

然后按照上述的修改Student 类,添加age字段和对应方法,然后对 stu.bin 文件进行反序列化。控制台输出结果如下:

Student{name='苏星', address='北京', age=0}

可见,我们序列化成功了,age属性默认为0。

5、transient 和 static 修饰的字段

开门见山,被transient修饰的字段是不参与序列化的,该关键字也为此而生。

但是,被 static 修饰的字段也不参与序列化。

我们为 address 字段 添加 static 关键字,用相同的方式进行序列化,然后进行反序列化,控制台输出结果:

Student{name='苏星', address='null', age=21}

我们发现 address 字段为 null ,说明被static 修饰的 address 没有参与序列化。

transient 关键字与static类似,transient修饰的字段也不参与对象的序列化。

6、同时序列化多个对象

方法一:顺序序列化

同时调用多个序列化方法,把多个对象序列化到一个.bin文件中。

示例:

序列化

package basis.StuIO.StuObjectStream;

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

public class StuObjectStream {
    public static void main(String[] args) throws Exception {
        //序列化
       //创建待序列化的对象
        Student zhangsan = new Student("张三","北京",21);
        Student lisi = new Student("李四","天津",22);
        Student wangwu = new Student("王五","南京",24);

        //创建用于序列化的对象输出流,将对象写入到项目根目录下的一个二进制文件中
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("stu.bin"));

        oos.writeObject(zhangsan);
        oos.writeObject(lisi);
        oos.writeObject(wangwu);

        System.out.println("序列化完成。");
        
    }
}package basis.StuIO.StuObjectStream;

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

public class StuObjectStream {
    public static void main(String[] args) throws Exception {
        //序列化
       //创建待序列化的对象
        Student zhangsan = new Student("张三","北京",21);
        Student lisi = new Student("李四","天津",22);
        Student wangwu = new Student("王五","南京",24);

        //创建用于序列化的对象输出流,将对象写入到项目根目录下的一个二进制文件中
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("stu.bin"));

        oos.writeObject(zhangsan);
        oos.writeObject(lisi);
        oos.writeObject(wangwu);

        System.out.println("序列化完成。");
        
    }
}

反序列化

package basis.StuIO.StuObjectStream;

import java.io.FileInputStream;
import java.io.ObjectInputStream;

public class StuObjectStream {
    public static void main(String[] args) throws Exception {
        //反序列化
        Student zhangsan = null;
        Student lisi = null;
        Student wangwu = null;

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("stu.bin"));
        zhangsan = (Student) ois.readObject();
        lisi = (Student) ois.readObject();
        wangwu = (Student) ois.readObject();

        System.out.println(zhangsan);
        System.out.println(lisi);
        System.out.println(wangwu);

        System.out.println("反序列化完成。");
    }
}

控制台输出:

Student{name='张三', address='null', age=21}
Student{name='李四', address='null', age=22}
Student{name='王五', address='null', age=24}
反序列化完成。

由结果可以看出,序列化多个对象成功了,因为address字段是 static 的,所以不参与序列化,这里反序列化时,该字段为空。

方法二、借助集合:

创建多个对象放入集合中,然后对集合进行序列化。

有集合的类声明可以看出,ArrayList 类已经实现了Serializable 接口,并且添加了序列化版本号。

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    private static final long serialVersionUID = 8683452581122892189L;
}

示例:

序列化:

package basis.StuIO.StuObjectStream;

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

public class StuListSerialiazble {
    public static void main(String[] args) throws Exception{
        Student zhangsan = new Student("张三","北京",21);
        Student lisi = new Student("李四","天津",22);
        Student wangwu = new Student("王五","南京",24);

        List<Student> students = new ArrayList<>();

        students.add(zhangsan);
        students.add(lisi);
        students.add(wangwu);

        ObjectOutputStream oos  = new ObjectOutputStream(new FileOutputStream("stuList.bin"));

        oos.writeObject(students);

        System.out.println("序列化完成。");
    }
}

反序列化:

package basis.StuIO.StuObjectStream;

import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class StuListSerialiazble {
    public static void main(String[] args) throws Exception{
        List<Student> students = new ArrayList<>();

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("stuList.bin"));

        students = (List<Student>) ois.readObject();

        Iterator<Student> it = students.iterator();
        while(it.hasNext()){
            System.out.println(it.next().toString());
        }
    }
}

控制台输出:

Student{name='张三', address='null', age=21}
Student{name='李四', address='null', age=22}
Student{name='王五', address='null', age=24}

反序列化成功。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值