# 第 15 章:随堂复习与企业真题(File 类与 IO 流)


# 一、随堂复习

# 1. File 类的使用

  • File 类的一个实例对应着 磁盘上的一个文件或文件目录 。 ----> “万事万物皆对象”
  • (熟悉)File 的实例化、常用的方法
  • File 类中 只有新建、删除、获取路径等方法,不包含读写文件的方法,此时需要使用IO流

# 2. IO 流的概述

  • IO 流的分类
    • 流向:输入流、输出流
    • 处理数据单位:字节流、字符流
    • 流的角色: 节点流处理流
  • IO 的 4 个抽象基类: InputStream \ OutputStream \ Reader \ Writer
image-20230411183512545

# 3. 节点流之:文件流

  • FileInputStream \ FileOutputStream \ FileReader \ FileWriter
  • (掌握)读写数据的过程。
    • 步骤 1:创建 File 类的对象,作为读取或写出数据的端点
    • 步骤 2:创建相关的流的对象
    • 步骤 3:读取、写出数据的过程
    • 步骤 4:关闭流资源

# 4. 处理流之一:缓冲流

  • BufferedInputStream \ BufferedOutputStream \ BufferedReader \ BufferedWriter
  • 作用:实现更高效的读写数据的操作

# 5. 处理流之二:转换流

  • 层次 1:熟悉转换流的使用
    • InputStreamReaderOutputStreamWriter
  • 层次 2:(掌握)字符的编码和解码的过程、常用的字符集
    • 解决相关的问题:读写字符出现乱码!本质问题:使用的解码集与编码集不一致。

# 6. 处理流之三:对象流

  • 层次 1:熟悉对象流的使用
    • ObjectInputStream :反序列化时需要使用的 api
    • ObjectOutputStream :序列化时需要使用的 api
  • 层次 2:对象的序列化机制
    • 使用场景:不同的进程之间通信、客户端(或浏览器端)与服务器端传输数据
    • 自定义类要想实现序列化机制需要满足的要求及注意点。
image-20230411144529975

# 7. 其它流的使用

  • 了解:数据流:DataInputStream 、DataOutputStream
  • 了解:标准的输入流、标准的输出流: System.inSystem.out
  • 了解:打印流: PrintStreamPrintWriter

# 二、企业真题

# 2.1 IO 流概述

# 1. 谈谈 Java IO 里面的常用类,字节流,字符流 (银 * 数据)

image-20230411183512545

操作数据单位 的不同分为:

  • ** 字节流(8bit)** 用于处理二进制数据,常用的类有 InputStreamOutputStream
    • 常用的类有 FileInputStreamFileOutputStream ,它们分别用于从文件中读取和写入 字节 数据。
  • ** 字符流(16bit)** 用于处理文本数据,常用的类有 ReaderWriter
    • 常用的类有 FileReaderFileWriter ,它们分别用于从文件中读取和写入 字符 数据。

根据 IO流的角色 不同分为:节点流处理流

  • 节点流:直接从数据源目的地读写数据

    image-20220412230745170

  • 处理流:不直接连接到数据源或目的地,而是“连接” 在已存在的流(节点流或处理流)之上,通过对数据的处理为程序提供更为强大的读写功能。

    image-20220412230751461

    • 缓冲流BufferedInputStream , BufferedOutputStream , BufferedReader , BufferedWriter

    • 数据流DataInputStream , DataOutputStream )等

# 2. Java 中有几种类型的流?JDK 为每种类型的流提供一些抽象类以供继承,请说出他们分别是哪些类?(上海 * 厦 * 联网、极 * 科技)

在 Java 中,有四种类型的流:字节输入流字节输出流字符输入流字符输出流。JDK 为每种类型的流都提供了抽象类以供继承。

字节输入流的抽象类是 InputStream ,它定义了读取字节数据的方法。字节输出流的抽象类是 OutputStream ,它定义了写入字节数据的方法。

字符输入流的抽象类是 Reader ,它定义了读取字符数据的方法。字符输出流的抽象类是 Writer ,它定义了写入字符数据的方法。

image-20230410101610691

# 3. 流一般需不需要关闭?如果关闭的话用什么方法?处理流是怎么关闭的?(银 * 数据)

是的,流在使用完毕后通常 需要关闭 。这样可以释放系统资源, 防止资源泄漏 。可以使用 close() 方法来关闭流。

在处理流时,只需要 关闭最外层的流即可 。当最外层的流被关闭时,它内部包装的所有流都会被自动关闭。

例如,如果我们使用 BufferedReader 来包装 FileReader ,那么只需要调用 BufferedReaderclose() 方法即可,它会自动关闭内部的 FileReader

BufferedReader br = new BufferedReader(new FileReader("file.txt"));
// ...
br.close(); // 关闭 BufferedReader,同时也会关闭内部的 FileReader

在 Java 7 及以上版本中,还可以使用 try-with-resources 语句来自动关闭流。只需将流的声明放在 try 语句的括号中,当 try 语句块执行完毕后,流会被自动关闭。

try (BufferedReader br = new BufferedReader(new FileReader("file.txt"))) {
    // ...
} // 当 try 语句块执行完毕后,br 会被自动关闭

# 4. OutputStream 里面的 write () 是什么意思?(君 * 科技)

OutputStream 类中的 write() 方法用于将指定的字节数据写出到输出流。它有三种重载形式:

  • write(int b) :将指定的字节( b )写出到输出流。
  • write(byte[] b) :将指定字节数组( b )中的所有字节写出到输出流。
  • write(byte[] b, int off, int len) :将指定字节数组( b )中从偏移量( off )开始的 len 个字节写出到输出流。

例如,下面的代码演示了如何使用 FileOutputStream 将字符串写入文件:

String str = "Hello, world!";
try (FileOutputStream fos = new FileOutputStream("file.txt")) {
    fos.write(str.getBytes());
}

# 2.2 缓冲流

# 1. BufferedReader 属于哪种流?他主要是用来做什么的?(国 * 电网)

属于 字符输入流 ,它继承自 Reader 类。它的主要作用是为其他字符输入流(如 FileReader )提供缓冲功能,以提高读取效率

当我们从 BufferedReader 中读取数据时,它会一次性从底层输入流中读取多个字符并存储在 内部缓冲区。这样,当我们再次读取数据时,就可以直接从缓冲区中获取,而不需要再次访问底层输入流

此外, BufferedReader 还提供了一些方便的方法,如 readLine() ,用于读取一行文本,不包括换行符

例如,下面的代码演示了如何使用 BufferedReader 来读取文件中的文本:

try (BufferedReader br = new BufferedReader(new FileReader("file.txt"))) {
    String line;
    while ((line = br.readLine()) != null) {
        System.out.println(line);
    }
}

# 2. 什么是缓冲区?有什么作用?(北京中油 **)

内部缓冲区是指缓冲流类(如 BufferedInputStreamBufferedOutputStreamBufferedReaderBufferedWriter )内部用于临时存储数据的数组,类型为 byte[]char[] ,大小默认是 8192 ,当达到一定程度时,集中性的写出。它的作用是减少与磁盘的交互次数,从而提高读写效率

例如,在使用 BufferedReader 读取数据时,它会一次性从底层输入流中读取多个字符(char)并存储在内部缓冲区中。这样,当我们再次读取数据时,就可以直接从缓冲区中获取,而不需要再次访问底层输入流。这样可以减少对底层输入流的访问次数,从而提高读取效率。

同理,在使用 BufferedWriter 写入数据时,它会先将数据写入内部缓冲区。当缓冲区满时,才会将数据一次性写入底层输出流。这样可以减少对底层输出流的访问次数,从而提高写入效率。

# 2.3 转换流

# 1. 字节流和字符流是什么?怎么转换?(北京蓝 *、* 海 * 供应链管理)

字节流用于处理 二进制数据 ,常用的类有 InputStreamOutputStream

字符流用于处理 文本数据 ,常用的类有 ReaderWriter

在某些情况下,我们需要将字节流和字符流进行转换。例如,当我们从网络套接字( Socket )中读取文本数据时,需要将套接字的输入流(字节流)转换为字符流。

可以使用处理流中的转换流来实现,常用的类有 InputStreamReaderOutputStreamWriter用于实现字节流和字符流之间的转换。这两个类分别继承自 ReaderWriter ,它们可以将字节流转换为字符流,或将字符流转换为字节流。

例如,下面的代码演示了如何使用 InputStreamReader 将套接字的输入流(字节流)转换为字符流:

Socket socket = ...;
try (BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
    String line;
    while ((line = br.readLine()) != null) {
        System.out.println(line);
    }
}

# 2.4 序列化

# 1. 什么是 Java 序列化,如何实现 (君 * 科技、上海 * 厦物联网)

** 对象序列化机制 ** 允许内存中的 Java 对象转换成平台无关的二进制流(字节序列),以便将其存储在文件中或通过网络传输。与之相反的过程称为反序列化,即从字节序列中恢复对象。当其它程序获取了这种二进制流,就可以恢复成原来的 Java 对象。

要实现 Java 序列化,需要满足以下条件:

  • 对象所属的类必须实现 Serializable 接口。这个接口是一个 标记接口 ,没有任何方法,只是用来标识一个类是否支持序列化。
  • 对象中所有需要序列化的字段都必须是可序列化的。如果有不可序列化的字段,则需要将其声明为 transient ,以便在序列化时跳过该字段。

可以使用 ObjectOutputStream 类来实现对象的序列化。它继承自 OutputStream ,提供了 writeObject() 方法用于将对象写入输出流。

例如,下面的代码演示了如何使用 ObjectOutputStream 将一个对象序列化并写入文件:

MyObject obj = new MyObject();
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("file.ser"))) {
    oos.writeObject(obj);
}

与之相反,可以使用 ObjectInputStream 类来实现对象的反序列化。它继承自 InputStream ,提供了 readObject() 方法用于从输入流中读取对象。

例如,下面的代码演示了如何使用 ObjectInputStream 从文件中读取并反序列化一个对象:

try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("file.ser"))) {
    MyObject obj = (MyObject) ois.readObject();
}

序列化机制的实现步骤如下:

  1. 自定义需要实现 java.io.Serializable标识接口 ,否则在序列化时报错 NotSerializableException

  2. 自定义类需要显示声明一个静态常量: static final long serialVersionUID ,用来唯一标识当前类,值可以任意指定

    如果类没有显示定义 serialVersionUID,它的值是 Java 运行时环境根据类的内部细节 自动生成 的。若序列化后,类的实例变量做了修改, serialVersionUID 可能发生变化 ,在反序列化时会因为序列版本号不匹配,导致反序列化时报错 InvalidClassException

    如果声明了 serialVersionUID ,即使在序列化完成之后修改了类,导致类重新编译,则原来的数据也能正常反序列化,只是新增的字段值是默认值而已。

    因此,建议显式声明 serialVersionUID 。

  3. 自定义类的各个属性如果也要序列化的话

    1. 对于基本数据类型,默认是可序列化的
    2. 对于引用数据类型,要求实现 Serializable 接口,否则报错 NotSerializableException
    • 如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用 transient 关键字修饰

    • 静态( static )变量的值不会序列化,因为静态变量的值不属于某个对象

image-20230411144529975

# 2. Java 有些类中为什么需要实现 Serializable 接口?(阿 * 校招)

便于此类的对象实现序列化操作

当一个类需要支持序列化时,它必须实现 Serializable 接口。序列化是指将对象转换为字节序列的过程,以便将其存储在文件中或通过网络传输。只有实现了 Serializable 接口的类才能被序列化。

Serializable 接口是一个标记接口,没有任何方法。它只是用来标识一个类是否支持序列化。当我们试图序列化一个未实现 Serializable 接口的对象时,会抛出 NotSerializableException 异常。

许多 Java 类都实现了 Serializable 接口,以便支持序列化。例如,Java 集合框架中的许多类(如 ArrayList , HashMap 等)都实现了这个接口。