• 【Java 基础篇】Java 对象序列化流详解


    在这里插入图片描述

    Java对象序列化流是Java编程中用于序列化和反序列化对象的机制之一。它允许我们将对象转换为字节序列,以便在网络上传输或将对象永久保存到磁盘上。本文将深入探讨Java对象序列化流的工作原理、用法以及一些注意事项。

    什么是对象序列化?

    在深入了解Java对象序列化流之前,我们需要了解什么是对象序列化。对象序列化是一种将Java对象转换为字节流的过程,以便在不同的Java虚拟机之间进行通信,或者将对象持久化到磁盘上。反序列化是将字节流还原为Java对象的过程。

    对象序列化提供了一种轻松地保存和还原Java对象状态的方法,而无需手动处理对象的字段。这对于分布式系统、持久性存储和跨平台数据交换非常有用。

    Java对象序列化流

    Java提供了两个主要的对象序列化流类:ObjectOutputStreamObjectInputStream。让我们逐步了解它们的用法和工作原理。

    ObjectOutputStream

    ObjectOutputStream用于将Java对象序列化为字节流。以下是使用ObjectOutputStream的基本步骤:

    1. 创建一个FileOutputStreamByteArrayOutputStream,用于将字节流写入文件或内存中。
    2. 创建ObjectOutputStream,将其链接到文件输出流或字节数组输出流。
    3. 使用writeObject方法将要序列化的对象写入ObjectOutputStream
    4. 最后,关闭ObjectOutputStream以确保所有缓冲的数据都被刷新到底层流。

    下面是一个示例,演示如何使用ObjectOutputStream将一个Person对象序列化到文件中:

    import java.io.FileOutputStream;
    import java.io.ObjectOutputStream;
    import java.io.Serializable;
    
    public class Person implements Serializable {
        private String name;
        private int age;
    
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        public static void main(String[] args) {
            try (FileOutputStream fileOut = new FileOutputStream("person.ser");
                 ObjectOutputStream objectOut = new ObjectOutputStream(fileOut)) {
    
                Person person = new Person("Alice", 30);
                objectOut.writeObject(person);
                System.out.println("Person object serialized successfully.");
    
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    ObjectInputStream

    ObjectInputStream用于从字节流反序列化Java对象。以下是使用ObjectInputStream的基本步骤:

    1. 创建一个FileInputStreamByteArrayInputStream,用于从文件或内存中读取字节流。
    2. 创建ObjectInputStream,将其链接到文件输入流或字节数组输入流。
    3. 使用readObject方法从ObjectInputStream读取反序列化的对象。
    4. 最后,关闭ObjectInputStream

    下面是一个示例,演示如何使用ObjectInputStream从文件中反序列化一个Person对象:

    import java.io.FileInputStream;
    import java.io.ObjectInputStream;
    import java.io.Serializable;
    
    public class Person implements Serializable {
        // ... (与上面相同的Person类定义)
    
        public static void main(String[] args) {
            try (FileInputStream fileIn = new FileInputStream("person.ser");
                 ObjectInputStream objectIn = new ObjectInputStream(fileIn)) {
    
                Person person = (Person) objectIn.readObject();
                System.out.println("Name: " + person.name + ", Age: " + person.age);
    
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    Serializable接口

    在上述示例中,Person类实现了Serializable接口。这是一个标记接口,表示该类可以进行对象序列化。如果一个类没有实现Serializable接口,试图对其进行序列化将引发java.io.NotSerializableException异常。

    需要注意的是,如果一个类的某些字段不应该被序列化,可以使用transient关键字来标记这些字段。这些字段将被忽略,不会包含在序列化的输出中。

    序列化版本UID

    每个可序列化的类都有一个称为序列化版本UID(SerialVersionUID)的唯一标识符。这是一个64位的long型数值,用于标识类的版本。如果您在序列化和反序列化过程中更改了类的结构,可能会导致版本不匹配,从而引发InvalidClassException异常。为了避免这种情况,可以显式定义版本UID,如下所示:

    private static final long serialVersionUID = 123456789L;
    
    • 1

    序列化与安全性

    需要格外小心的是,反序列化操作可能存在安全风险,因为它可以执行来自未受信任来源的代码。要确保安全性,可以采取以下措施:

    1. 不要反序列化不受信任的数据:只反序列化来自受信任来源的数据,或对数据进行严格验证。
    2. 使用readResolve方法:如果您的类包含敏感信息,可以实现readResolve方法,以确保反序列化后的对象是单一实例。

    常见用法及注意事项

    当涉及到Java对象序列化流的更多用法时,有一些高级功能和技巧,可以让您更好地掌握该主题。

    自定义序列化与反序列化

    虽然Java对象序列化机制会自动将对象的字段序列化,但有时您可能需要更多的控制权来自定义序列化和反序列化过程。这可以通过实现以下两个方法来实现:

    1. writeObject方法:您可以在类中定义一个名为writeObject的方法,该方法会在对象被序列化时自动调用。您可以在这个方法中指定自定义的序列化逻辑,以满足特定需求。注意,这个方法必须是private的。

      private void writeObject(ObjectOutputStream out) throws IOException {
          // 自定义序列化逻辑
          out.defaultWriteObject(); // 调用默认的序列化逻辑
      }
      
      • 1
      • 2
      • 3
      • 4
    2. readObject方法:类似地,您可以定义一个名为readObject的方法,该方法会在对象被反序列化时自动调用。在这个方法中,您可以实现自定义的反序列化逻辑,以还原对象状态。

      private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
          // 自定义反序列化逻辑
          in.defaultReadObject(); // 调用默认的反序列化逻辑
      }
      
      • 1
      • 2
      • 3
      • 4

    通过自定义这两个方法,您可以在序列化和反序列化过程中实现一些高级逻辑,例如加密、压缩或版本兼容性处理。

    序列化集合和嵌套对象

    Java对象序列化机制能够处理包含集合和嵌套对象的复杂对象图。这意味着您可以序列化包括ArrayListHashMap等集合对象,以及包含其他自定义序列化对象的复合对象。

    import java.io.Serializable;
    import java.util.ArrayList;
    import java.util.List;
    
    public class Student implements Serializable {
        private String name;
        private List<Course> courses;
    
        public Student(String name) {
            this.name = name;
            this.courses = new ArrayList<>();
        }
    
        public void addCourse(Course course) {
            courses.add(course);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    在这个示例中,Student类包含一个List字段,用于存储学生所选的课程。Course类也必须是可序列化的。

    序列化版本控制

    当您需要对类进行更改时,为了确保版本兼容性,可以使用serialVersionUID来控制序列化版本。如果您对一个已经序列化的类进行更改,并且没有提供serialVersionUID,Java会根据类的结构自动生成版本号。这可能会导致反序列化失败。

    private static final long serialVersionUID = 123456789L;
    
    • 1

    通过显式定义serialVersionUID,您可以确保在对类进行更改后,仍然可以反序列化旧版本的对象。

    序列化与跨平台通信

    Java对象序列化机制允许不同平台上的Java程序进行通信。这意味着您可以在不同操作系统和编程语言之间传递序列化的Java对象。这在构建分布式系统或与其他非Java应用程序进行通信时非常有用。

    要注意的是,跨平台通信需要确保所有参与的系统都能理解序列化格式。此外,不同版本的Java可能会有不同的序列化实现,因此需要谨慎处理版本兼容性。

    序列化的性能和安全性考虑

    虽然对象序列化提供了方便的方式来序列化和反序列化对象,但它可能会对性能产生一定的影响,尤其是在序列化大型对象时。在性能敏感的应用程序中,可能需要考虑替代的序列化方法,如JSON或Protocol Buffers。

    此外,由于反序列化操作可能存在安全风险,反序列化不受信任的数据时需要格外小心。一些安全性措施包括限制反序列化操作,仅反序列化来自受信任源的数据,或对反序列化数据进行有效的验证。

    总之,Java对象序列化提供了一种强大的机制来序列化和反序列化对象,但需要谨慎使用,并在高级用法中考虑性能和安全性问题。通过了解这些高级用法,您可以更好地应对复杂的序列化需求。

    应用场景

    Java对象序列化通常用于以下情况:

    • 网络通信:通过序列化和反序列化,可以在网络上轻松传输对象。
    • 持久性存储:将对象保存到文件或数据库中,以便以后重新加载。
    • 分布式系统:在分布式环境中,不同虚拟机之间可以通过序列化和反序列化来传递消息和数据。

    总结

    Java对象序列化流提供了一种方便的方式来序列化和反序列化Java对象,以便在不同的应用程序和环境中传输和存储数据。在使用时,需要注意实现Serializable接口、管理版本UID以及处理潜在的安全性问题。通过了解和掌握对象序列化,您可以更好地应对分布式系统、网络通信和数据持久性等方面的编程需求。

  • 相关阅读:
    单片机开发——宠物自动饮水器方案
    卫星结构。。。
    遥感图像云检测数据集CHLandsat8
    Elasticsearch 重启后不正常,索引部分为Red
    3.6研究代码(2)
    Springboot之拦截器Interceptor
    Spring 加强版 ORM 框架 spring-data-jpa 入门与实践
    window10环境构建和运行skia源码
    网络安全 记录
    Edge浏览器如何简单用上ChatGPT
  • 原文地址:https://blog.csdn.net/qq_21484461/article/details/133048831