去皮卡多 发表于 2024-12-28 16:36:36

Java难绷知识01——IO流的对象流

Java难绷知识01之对象流

本篇文章会探讨一些JavaIO流中比力容易被忽视的对象流,而且会相对的探讨其中的一些细节
其中对于对象流的操纵讲解会少一些,主要讨论的是一些细节
在 Java IO 流中,对象流(ObjectInputStream对象输入流 和 ObjectOutputStream对象输出流)用于将对象举行序列化和反序列化操纵
对象流及其序列化

首先,ObjectInputStream和ObjectOutputStream这两个类都属于是字节流,它们分别继续自InputStream和OutputStream
对象输出流,ObjectOutputStream,用于对象的序列化,也就是把Java对象转换成字节序列,把字节序列写出到文件,以这种对象转换为字节序列的机制实现了对象存储
序列化目的是能够将整个 Java 对象(包括对象的状态,即成员变量的值)转换为字节流,以便在网络上传输或存储到文件中,之后还能稳定的从字节流中恢复出原来的对象。
通过序列化,对象的状态信息(包括成员变量的值)可以被保存下来,以便后续传输或存储。
对象输入流,ObjectInputStream,用于对象的反序列化,是将文件中的字节序列恢复为Java对象,以这种字节序列转换为对象的机制实现了对象读取
反序列化时,系统会根据字节流中的信息重新构建对象的状态。
https://img2024.cnblogs.com/blog/3460931/202412/3460931-20241228161731123-279514454.png
在其基础上,我们再探讨序列化的一些细节
对象序列化的条件

要想使一个类的对象能够被序列化,该类必须实现 java.io.Serializable 接口
这是一个标记接口,没有任何方法需要实现。实现该接口,意味着告诉 Java 虚拟机这个类的对象可以被序列化。
点击查看代码import java.io.*;

// 定义一个实现Serializable接口的类
class Person implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    private int age;

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

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

public class SerializationExample {
    public static void main(String[] args) {
      // 创建一个对象
      Person person = new Person("Alice", 30);

      // 序列化对象到文件
      try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.ser"))) {
            oos.writeObject(person);
            System.out.println("对象已成功序列化到文件 person.ser");
      } catch (IOException e) {
            e.printStackTrace();
      }

      // 从文件反序列化对象
      try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.ser"))) {
            Person deserializedPerson = (Person) ois.readObject();
            System.out.println("反序列化后的对象: " + deserializedPerson);
      } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
      }
    }
}注意:
每个可序列化的类都应该有一个serialVersionUID,用于验证序列化和反序列化过程中的版本兼容性
使用transient关键字声明的成员变量不会被序列化
序列化可能会引发安全题目
对象流的使用细节

对象流需要关闭吗

先说结论,可以不关
但是强烈建议关闭。这与 Java 流的资源管理机制紧密相关。
涉及到对外部资源的读写操纵,包括网络、硬盘等等的I/O流,如果在使用完毕之后不关闭,会导致资源走漏以及可能会引起文件锁定等题目。
当我们使用流举行数据操纵时,它们会占用系统资源,如文件句柄、网络连接等。如果不关闭流,这些资源将不会被释放,可能导致资源走漏题目。长时间运行的程序如果频繁出现资源走漏,最终可能耗尽系统资源,导致程序崩溃或系统性能严重下降。
而且关闭流可以确保所有已写入的数据被正确地传输到目的位置
比方,当使用 ObjectOutputStream 将对象写入文件时,关闭流会保证缓冲区中的所有数据都被写入文件,制止数据丢失。
通常建议使用try - with - resources语句来主动关闭流
示例代码import java.io.*;

class MyClass implements Serializable {
    private static final long serialVersionUID = 1L;
    int data;

    public MyClass(int data) {
      this.data = data;
    }
}

public class SerializeExample {
    public static void main(String[] args) {
      MyClass obj = new MyClass(42);
      try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("myfile.ser"))) {
            oos.writeObject(obj);
      } catch (IOException e) {
            e.printStackTrace();
      }
    }
}对象流需要使用flush吗

先说结论,不用
ObjectOutputStream内部维护了一个缓冲区。在调用writeObject方法时,数据首先会被写入缓冲区。调用flush方法可以强制将缓冲区中的数据立即写入底层输出流。然而,在大多数情况下,并不需要显式调用flush。因为当缓冲区满、流关闭大概调用某些特定方法(如writeObject在某些情况下会触发缓冲区数据的刷新)时,缓冲区的数据会主动被写入底层流。
当然特意需要弄的时候可以弄
serialVersionUID在对象流中的必要性

凡是实现Serializable接口(标识接口)的类都有一个表现序列化版本标识符的静态常量:
private static final long serialVersionUID;
来表明类的不同版本间的兼容性。
在完成序列化操纵后,如果对序列化对象举行了修改,那么我们再举行反序列化就会抛出InvalidClassException异常。 因为serialVersionUID缺失(没有显式分配)大概serialVersionUID发生了变革, serialVersionUID的作用在此体现——对序列化对象举行版本控制,有关各版本反序加化时是否兼容。制止混乱。
class Person implements Serializable {
    private static final long serialVersionUID = 1234567890123456789L;
    private String name;
    private int age;
    // 后续可以安全地对类结构进行一些兼容的修改
}一个类在没有显示界说这个静态变量,它的值是Java运行时情况根据类的内部细节主动生成的。
若类的实例变量做了修改,serialVersionUID 可能发生变革。所以建议一般显式声明。
序列化的一些细节

static修饰的属性 不可以被序列化
原因在于 static 变量属于类,而不是类的实例。它们是类级别的共享数据,与对象的状态无关。
序列化的目的是保存对象的状态,所以 static 变量不会被包含在序列化的内容中。
class StaticExample implements Serializable {
    private static final long serialVersionUID = 1L;
    private static int sharedValue = 10;
    private int instanceValue;

    public StaticExample(int instanceValue) {
      this.instanceValue = instanceValue;
    }
}当我们序列化 StaticExample 类的对象时,sharedValue 不会被序列化。
在反序列化时,sharedValue 的值将取决于类加载时的初始化状态,而不是序列化时的值
transient 修饰的属性不可以被序列化
transient 关键字用于标记那些不希望被序列化的属性。
这在某些场景下非常有用,好比当对象的某个属性包含敏感信息(如密码)大概该属性在反序列化后可以通过其他方式重新计算得到时。
class TransientExample implements Serializable {
    private static final long serialVersionUID = 1L;
    private String username;
    private transient String password;

    public TransientExample(String username, String password) {
      this.username = username;
      this.password = password;
    }
}当对 TransientExample 对象举行序列化时,password 的值不会被写入序列化流。这样可以掩护敏感信息,防止其在序列化过程中被泄露。在反序列化后,password 属性的值将为 null,程序可以重新设置该值。
对象序列化的作用

为什么JavaIO流要特意造出这么一个IO流
当你需要把对象写入到文件大概读取的时候,其实我们更多的情况通常是保存对象的有用值字段,也就是对象的详细实例的字段,那么使用文件操纵流FileOutputStream和字符输出流BufferedWriter或PrintStream就充足。这些流可以用于处理文件写入和基本数据类型及字符串的输出,但对象序列化有着独特且不可替代的作用
网络传输对象

对象序列化机制是Java内建的一种对象长期化方式,可以很容易实现在JVM中的活动对象与字节流之间举行转换
在网络传输中,发送端将对象序列化成字节流,经过网络传输到网络的另一端,可以从字节流重新还原为对象,这个特点使得在举行端到端的网络传输数据时候,字节流和Java对象之间的转换稳定且快速。
在其中分布式系统中,不同的节点之间需要举行对象的转达,对象序列化使得这种对象传输变得简单直接,确保了对象在不同 Java 虚拟机之间的准确传输,即使这些 JVM 运行在不同的操纵系统上。
RMI 是 Java 的一种远程方法调用机制,它答应一个 JVM 中的对象调用另一个 JVM 中的对象的方法。对象序列化也在其中起着关键作用
确保对象深层次的复制和长期化

当对一个对象举行序列化然后反序列化时,会得到一个与原对象状态完全雷同但内存地点不同的新对象。可以实现在不影响原对象的情况下对对象举行操纵,多线程下的数据处理的机制也有一定序列化和反序列化的影子。
这种情况对于远程创建对象副本并且调理的时候十分方便,而且不会干扰对象内部包含的复杂的引用关系,合理使用对象流可以大大提高程序处理复杂数据的能力。
在许多情况下,对象内部状态是需要被长期化的,序列化通过把对象写为字节流,保存的位置从JVM内存转移到文件系统,在需要的时候随时可以举行快速方便的还原
比方:在一个游戏中,可以使用对象流将玩家的游戏进度(一个复杂的对象,包含玩家角色信息、游戏关卡等)保存到文件中,下次玩家启动游戏时可以恢复到上次的进度。
对象流与其他流的关系

与字节流的关系

字符流是对象流的特例,它们处理的是字符数据的序列化和反序列化。
字符流(Reader 和 Writer 及其子类,如 BufferedReader、BufferedWriter 等)主要用于处理字符数据。Java 采取 Unicode 编码来表现字符,字符流对其的输入输出有优化。而且字符流在处理数据时,会根据指定的字符编码举行字节和字符之间的转换
而对象流可以将任何实现了 Serializable 接口的对象举行序列化和反序列化,意味着对象流操纵的数据是复杂的对象布局,包括对象的成员变量、对象之间的引用关系。
与缓冲流的关系

功能叠加:缓冲流(如 BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter)的主要作用是提高数据读写的效率,通过在内存中设置缓冲区,减少现实的 I/O 操纵次数。
对象流可以和缓冲流结合使用,以提拔对象序列化和反序列化的性能。
可以将 ObjectOutputStream 包装在 BufferedOutputStream 中,这样在写入对象时,数据会先写入缓冲区,当缓冲区满或流关闭时,才会一次性将数据写入底层输出流,从而减少磁盘 I/O 操纵的频率,提高写入效率。
在处理大量对象的序列化或反序列化时,结合缓冲流能显着提拔性能。不外反序列化涉及到的种种安全关系,这种情况,讨论一下就好。
希望指点出需要补充的内容和题目,水平一般,需要大家的帮助才气更好

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: Java难绷知识01——IO流的对象流