【IO流系列】ObjectStream 序列化流与反序列化流

文章中的部分照片来源于哔站黑马程序员阿伟老师处,仅用学习,无商用,侵权联系删除!

1. 概述

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

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


主要的序列化流和反序列化流有以下两种:

  1. ObjectOutputStream(序列化流)ObjectOutputStream 是用于将 Java 对象序列化为字节流的类。通过将对象写入 ObjectOutputStream,对象会被转换为字节序列,并可以保存到文件、网络等介质中。

  2. ObjectInputStream(反序列化流)ObjectInputStream 是用于从字节流中反序列化对象的类。通过读取 ObjectInputStream 中的字节序列,可以将字节数据还原为原始的 Java 对象。

在这里插入图片描述

2. 作用

  1. 对象持久化存储:序列化流可以将 Java 对象转换为字节序列并保存到文件或数据库中,实现对象的持久化存储。这样可以在程序关闭后将对象数据保存在持久化介质中,下次程序运行时可以重新读取数据并还原为对象。

  2. 网络传输:通过序列化流,可以将对象转换为字节序列进行网络传输。在客户端和服务器之间传递对象数据时,可以使用序列化流将对象进行序列化后发送,接收方通过反序列化流还原对象。

  3. 跨平台数据交换:序列化流和反序列化流可以实现不同平台、不同语言之间对象数据的交换。在跨平台开发或不同系统之间数据传递时,可以使用序列化流将对象序列化为字节序列,从而实现数据的跨平台兼容性。

  4. 缓存对象:通过序列化流可以将对象存储在缓存中,提高程序性能。对象序列化后可以保存在内存或硬盘上,下次需要时直接反序列化而不用重新创建对象,节省了对象的创建时间。

3. 序列化流(对象操作字节输出流)

序列化流 /对象操作输出流:可以把Java中的对象写到本地文件中

3.1 构造方法

构造方法说明
public ObjectOutputStream(OutputStream out)把基本流包装成高级流
  • 构造方法:public ObjectOutputStream(OutputStream out)
    • 说明:将提供的 OutputStream 参数 out 包装成 ObjectOutputStream,使得可以使用 ObjectOutputStream 来将对象序列化并写入到这个输出流中。

3.2 成员方法

成员方法说明
public final void writeObject(Object obj)把对象序列化(写出)到文件中去
  • 方法:public final void writeObject(Object obj)
  • 说明:将传入的对象 obj 进行序列化,并将序列化后的数据写出到文件中。

3.3 代码示例

  • 代码示例
    • 测试类
    package text.IOStream.ObjectStream.ObjectStream01;
    
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.ObjectOutputStream;
    
    /*序列化流/对象操作输出流
    
    序列化流 /对象操作输出流:可以把lava中的对象写到本地文件中
    
    | 构造方法                                    | 说明                 |
    | ------------------------------------------- | -------------------- |
    | public ObjectOutputStream(OutputStream out) | 把基本流包装成高级流 |
    
    | 成员方法                                  | 说明                         |
    | ----------------------------------------- | ---------------------------- |
    | public final void writeObject(Object obj) | 把对象序列化(写出)到文件中去 |
    
    细节1:
        使用对象输出流将对象保存到文件时会出现NotSerializableException异常
    解决方案:需要让Javabean类实现Serializable接口
          Serializable接口里面是没有抽象方法,是个标记型接口
          一旦实现了这个接口,那么就表示当前的Student类可以被序列化
    细节2:
        序列化之后,修改Javabean类,再次反序列化,会出问题,会抛出InvalidClassException异常
        解决方案:给JavaBean类添加serialVersionUID的(序列号、版本号)
        添加序列号,java底层会在实现Serializable接口的类中生成个序列号,在输入文件时,也包含序列号
        当修改了实现Serializable接口的类时,序列号也会更改,当输入文件的序列号与原来实现Serializable接口的类的序列号不一致时就会报错,因此我们需要手动给实现Serializable接口的类设置序列号
    
    细节3:
        如果一个对象中的某个成员变量的值不想被序列化,可以给该成员日安家transient关键字修饰
        transient:瞬态关键字
        作用:不会把当前的属性序列化到本地文件中
    
    细节4:
        序列化流写到文件中的数据是不能修改的,一旦修改就无法再次读回来了。
    
    需求:
        利用序列化流/对象操作输出流,把一个对象写到本地文件中
     */
    public class ObjectStream01 {
        public static void main(String[] args) throws IOException {
            //创建学生对象
            Student stu = new Student("张三", 23, "南京");
            //创建序列化流(对象操作输出流)
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\JavaCode\\code\\codeText01\\src\\text\\IOStream\\ObjectStream\\ObjectStream01\\student"));
            //输出本地文件
            oos.writeObject(stu);
            //释放资源
            oos.close();
        }
    }
    
    
    • 标准的Javabean类
    package text.IOStream.ObjectStream.ObjectStream01;
    
    import java.io.Serial;
    import java.io.Serializable;
    
    //Serializable接口里面是没有抽象方法,是个标记型接口
    //一旦实现了这个接口,那么就表示当前的student类可以被序列化
    public class Student implements Serializable {
        //添加序列号,java底层会在实现Serializable接口的类中生成个序列号,在输入文件时,也包含序列号
        //当修改了实现Serializable接口的类时,序列号也会更改,当输入文件的序列号与原来实现Serializable接口的类的序列号不一致时就会报错,因此我们需要手动给实现Serializable接口的类设置序列号
        @Serial
        private static final long serialVersionUID = -5718005322642298180L;
    
    
        private String name;
        private int age;
        //transient:瞬态关键字
        //作用:不会把当前的属性序列化到本地文件中
        private transient String address;
    
    
        public Student() {
        }
    
        public Student(String name, int age, String address) {
            this.name = name;
            this.age = age;
            this.address = address;
        }
    
        /**
         * 获取
         *
         * @return name
         */
        public String getName() {
            return name;
        }
    
        /**
         * 设置
         *
         * @param name
         */
        public void setName(String name) {
            this.name = name;
        }
    
        /**
         * 获取
         *
         * @return age
         */
        public int getAge() {
            return age;
        }
    
        /**
         * 设置
         *
         * @param age
         */
        public void setAge(int age) {
            this.age = age;
        }
    
        /**
         * 获取
         *
         * @return address
         */
        public String getAddress() {
            return address;
        }
    
        /**
         * 设置
         *
         * @param address
         */
        public void setAddress(String address) {
            this.address = address;
        }
    
        public String toString() {
            return "Student{ name = " + name + ", age = " + age + ", address = " + address + "}";
        }
    }
    
    
  • 输出结果
    • student文件
      在这里插入图片描述

4. 反序列化流(对象操作字节输入流)

反序列化流 /对象操作输入流:把序列化到本地文件中的对象,读取到程序中来

4.1 构造方法

构造方法说明
public ObjectInputStream(InputStream out)把基本流变成高级流
  • 构造方法:public ObjectInputStream(InputStream in)
  • 说明:ObjectInputStream 是一个对象输入流,用于从输入流 in 中读取序列化的对象数据。

4.2 成员方法

成员方法说明
public Object readObject()可以把序列化到本地文件中的对象,读取到程序中来
  • 方法:public Object readObject()
  • 说明:readObject() 方法用于从对象输入流中读取序列化的对象数据,并将其还原为原始的对象。该方法会尝试将读取的数据转换为 Object 类型,并需要进行类型转换为实际的对象类型。

4.3 代码示例

  • 代码示例
  • 需求:利用反序列化流/对象操作输入流,把文件中中的对象读到程序当中
    • 测试类
      package text.IOStream.ObjectStream.ObjectStream02;
      
      import java.io.FileInputStream;
      import java.io.IOException;
      import java.io.ObjectInputStream;
      
      /*反序列化流 /对象操作输入流
      
      反序列化流 /对象操作输入流:把序列化到本地文件中的对象,读取到程序中来
      
      | 构造方法                                  | 说明               |
      | ----------------------------------------- | ------------------ |
      | public ObjectInputStream(InputStream out) | 把基本流变成高级流 |
      
      | 成员方法                    | 说明                                           |
      | --------------------------- | ---------------------------------------------- |
      | public Object readObject () | 可以把序列化到本地文件中的对象,读取到程序中来 |
      
      细节1:
          使用对象输出流将对象保存到文件时会出现NotSerializableException异常
      解决方案:需要让Javabean类实现Serializable接口
            Serializable接口里面是没有抽象方法,是个标记型接口
            一旦实现了这个接口,那么就表示当前的Student类可以被序列化
      细节2:
          序列化之后,修改Javabean类,再次反序列化,会出问题,会抛出InvalidClassException异常
          解决方案:给JavaBean类添加serialVersionUID的(序列号、版本号)
          添加序列号,java底层会在实现Serializable接口的类中生成个序列号,在输入文件时,也包含序列号
          当修改了实现Serializable接口的类时,序列号也会更改,当输入文件的序列号与原来实现Serializable接口的类的序列号不一致时就会报错,因此我们需要手动给实现Serializable接口的类设置序列号
      
      细节3:
          如果一个对象中的某个成员变量的值不想被序列化,可以给该成员日安家transient关键字修饰
          transient:瞬态关键字
          作用:不会把当前的属性序列化到本地文件中
      
      细节4:
          序列化流写到文件中的数据是不能修改的,一旦修改就无法再次读回来了。
             
      需求:利用反序列化流/对象操作输入流,把文件中中的对象读到程序当中
       */
      public class ObjectStream02 {
          public static void main(String[] args) throws IOException, ClassNotFoundException {
              //创建反序列化流(对象操作输入流)
              ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\JavaCode\\code\\codeText01\\src\\text\\IOStream\\ObjectStream\\ObjectStream01\\student"));
              //输入数据
              Object student = ois.readObject();
              //打印学生对象
              System.out.println(student);
              //释放资源
              ois.close();
          }
      }
      
      
  • 输出结果
    在这里插入图片描述

5. 细节

  • 细节1: 使用对象输出流将对象保存到文件时会出现NotSerializableException异常

    • 解决方案:需要让Javabean类实现Serializable接口

      • Serializable接口里面是没有抽象方法,是个标记型接口

      • 一旦实现了这个接口,那么就表示当前的Student类可以被序列化

  • 细节2: 序列化之后,修改Javabean类,再次反序列化,会出问题,会抛出InvalidClassException异常

    • 解决方案:给JavaBean类添加serialVersionUID的(序列号、版本号)

      • 添加序列号,java底层会在实现Serializable接口的类中生成个序列号,在输入文件时,也包含序列号

      • 当修改了实现Serializable接口的类时,序列号也会更改,当输入文件的序列号与原来实现Serializable接口的类的序列号不一致时就会报错,因此我们需要手动给实现Serializable接口的类设置序列号

  • 细节3: 如果一个对象中的某个成员变量的值不想被序列化,可以给该成员日安家transient关键字修饰

    • transient:瞬态关键字

      • 作用:不会把当前的属性序列化到本地文件中
  • 细节4:序列化流写到文件中的数据是不能修改的,一旦修改就无法再次读回来了。

6. 练习

6.1 练习1:用对象流读写多个对象

  • 需求

    将多个自定义对象序列化到文件中,但是由于对象的个数不确定,反序列化该如何读取呢?

  • 代码示例

    • 序列化流(对象操作输出流)
    package text.IOStream.ObjectStream.ObjectStream03;
    
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.ObjectOutputStream;
    import java.util.ArrayList;
    
    /*用对象流读写多个对象
    需求:
        将多个自定义对象序列化到文件中,但是由于对象的个数不确定,反序列化该如何读取呢?
    
     */
    public class ObjectStream03 {
        public static void main(String[] args) throws IOException {
            //创建学生对象
            Student s1 = new Student("张三", 23, "天津");
            Student s2 = new Student("李四", 24, "南京");
            Student s3 = new Student("王五", 25, "北京");
            //创建集合,将学生对象添加进集合
            ArrayList<Student> list = new ArrayList<>();
            list.add(s1);
            list.add(s2);
            list.add(s3);
            //创建序列化流(对象操作输出流)
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\JavaCode\\code\\codeText01\\src\\text\\IOStream\\ObjectStream\\ObjectStream03\\a.txt"));
            //输出数据
            oos.writeObject(list);
            //释放资源
            oos.close();
        }
    }
    
    
    • 反序列化流(对象操作输入流)
    package text.IOStream.ObjectStream.ObjectStream03;
    
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.io.ObjectInputStream;
    import java.util.ArrayList;
    
    //反序列流(对象操作输入流)
    public class ObjectInput {
        public static void main(String[] args) throws IOException, ClassNotFoundException {
            //创建反序列化流对象(对象操作输入流)
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\JavaCode\\code\\codeText01\\src\\text\\IOStream\\ObjectStream\\ObjectStream03\\a.txt"));
            //输入数据
            ArrayList<Student> list = (ArrayList<Student>) ois.readObject();
            //遍历list集合
            for (Student student : list) {
                System.out.println(student);
            }
            //释放资源
            ois.close();
        }
    }
    
    
    • 标准的Javabean类
    package text.IOStream.ObjectStream.ObjectStream03;
    
    import java.io.Serial;
    import java.io.Serializable;
    
    public class Student implements Serializable {
        @Serial
        private static final long serialVersionUID = -8803236034300730365L;
        private String name;
        private int age;
        private transient String address;
    
    
        public Student() {
        }
    
        public Student(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        public Student(String name, int age, String address) {
            this.name = name;
            this.age = age;
            this.address = address;
        }
    
        public String getAddress() {
            return address;
        }
    
        public void setAddress(String address) {
            this.address = address;
        }
    
        /**
         * 获取
         *
         * @return name
         */
        public String getName() {
            return name;
        }
    
        /**
         * 设置
         *
         * @param name
         */
        public void setName(String name) {
            this.name = name;
        }
    
        /**
         * 获取
         *
         * @return age
         */
        public int getAge() {
            return age;
        }
    
        /**
         * 设置
         *
         * @param age
         */
        public void setAge(int age) {
            this.age = age;
        }
    
        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    ", address='" + address + '\'' +
                    '}';
        }
    }
    
    
  • 输出结果

    • a.txt
      在这里插入图片描述
    • 反序列化流(对象操作输入流)
      在这里插入图片描述

7. 注意事项

  1. 版本控制:对象在被序列化后,如果类发生了变化(例如字段新增、删除、修改),在反序列化时可能会出现不兼容的情况。为了避免这种问题,可以在类中使用序列化版本号来进行版本控制。

  2. transient关键字:在对象中使用 transient 关键字修饰的字段不会被序列化,这在某些情况下是很有用的,比如对于不需要序列化的敏感数据或临时计算结果。

  3. 对象引用:序列化后的对象图中可能包含重复的对象引用,这可能会导致数据的冗余性。在设计序列化对象时,可以考虑使用 writeObject() 和 readObject() 方法来处理对象引用,以减少序列化数据的冗余性。

  4. Serializable接口:要使一个类可以被序列化,需要实现 Serializable 接口。这个接口没有任何方法,只是用来标记类具有可序列化的能力。在进行序列化和反序列化时,确保类的所有字段都是可序列化的,否则可能会导致异常。

  5. 所有序列化类的父类必须是可序列化的:如果一个类实现了 Serializable 接口,那么它的所有父类都应该是可序列化的,否则可能会导致序列化异常。

  • 27
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

酷小洋

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值