缓冲区链接和修饰原则 Java I/O

Buffer chaining and decorative principle Java I/O

是的,我知道缓冲区是什么。但请注意:

BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("file.txt"));

缓冲实际上是如何工作的?在我看来,我们在 FileWriter 缓冲区而不是 BufferedWriter 缓冲区中缓冲数据。因为当BufferedWriter 的buffer 满了它会发送到FileWriter buffer 并负责写入数据?

我错过了什么吗?我的看法:看起来我们正在将水从一个较大的容器中吸到一个较小的容器中。所以我们最终从较小的那个倒水。

这里有类似的例子:

Scanner scanner = new BufferedReader(new FileReader("file.txt"));
scanner.nextLine();

这个我到处都见过。实际上,我们最终是逐行读取扫描仪,而不是从缓冲区及其 8k 容量读取。那么这里的缓冲区有什么意义呢?我们从文件中逐行读取,而不是一次读取整个缓冲区。 bufferedReader这里是冗余的吗?

请哪位大神解释一下,我纠结了好久

读取和写入数据的低级系统调用经过优化,可以一次传输更大的块。缓冲让您可以利用这一点。当您写入单个字符或短字符串时,它们会全部累积在一个缓冲区中,并在缓冲区已满时作为一个大块写出。当您读取数据时,读取函数请求填充一个大缓冲区,然后 returns 来自该缓冲区的数据。

你是对的,将缓冲流包装在其他缓冲流中是没有意义的:最好的情况是它什么也做不了,最坏的情况是它增加了开销,因为数据被不必要地从一个缓冲区复制到另一个缓冲区。最靠近数据源的缓冲区最重要。

另一方面,API 规范中没有任何内容说 FileWriter 和 FileReader 有缓冲区。事实上,它推荐wrap FileWriter within a BufferedWriter and FileReader within a BufferedReader:

For top efficiency, consider wrapping an OutputStreamWriter within a BufferedWriter so as to avoid frequent converter invocations. For example:

Writer out
  = new BufferedWriter(new OutputStreamWriter(System.out));

(FileWriter 是 OutputStreamWriter 的子class)

这在内部是如何工作的?

如果您查看 FileWriter 的实现方式,情况会变得复杂,因为 FileWriter 确实 涉及缓冲区。一些细节可能取决于您使用的 Java 版本。在 OpenJDK 中,当您创建一个装饰 FileWriter 的 BufferedWriter 时:

BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("file.txt"));

您正在创建一堆对象,如下所示,其中一个对象包裹下一个对象:

BufferedWriter -> FileWriter -> StreamEncoder -> FileOutputStream

实现了StreamEncoder is an internal class, part of how OutputStreamWriter

现在,当您将字符写入 BufferedWriter 实例时,它首先将它们累积在 BufferedWriter 自己的缓冲区中。内部 FileWriter 看不到任何数据,直到您写入足够的数据来填充此缓冲区(或调用 flush())。

BufferedWriter 缓冲区变满时,它会通过一次调用 write(char[],int,int) 将缓冲区的内容写入 FileWriter。这种大数据块的传输是效率的来源:现在 FileWriter 有一个大数据块可以写入文件,而不是单个字符。

然后它变得有点复杂:必须将字符转换为 字节 以便将它们写入文件。这是 FileWriter 将这些数据传递给 StreamEncoder 的地方。

StreamEncoder class 使用 CharsetEncoder 一次性将字符块转换为字节,并将字节累积在它自己的缓冲区中。完成后,它将字节作为一个块写入最里面的 FileOutputStream。 FileOutputStream 然后调用操作系统函数写入实际文件。

如果不使用 BufferedWriter 会怎样?

如果您直接将字符写入 FileWriter,它们将被传递到 StreamEncoder 对象,后者将它们转换为字节并存储在其专用缓冲区中,而不是直接写入 FileOutputStream。这样,FileWriter 的内部实现为您提供了一些缓冲的好处。但这不是 API 规范的一部分,因此您不应依赖它。

此外,每次调用 FileWriter.write 都会导致调用 CharsetEncoder 以将字符编码为字节。一次编码大块字符效率更高,写入单个字符或短字符串的开销更高。