对读取和写入数据的低级系统调用进行了优化,可以一次传输更大的块。缓冲让您可以利用这一点。当你写单个字符或短字符串时,它们都被累积在一个缓冲区中,当缓冲区已满时,它们作为一个大块写出。当您读取数据时,读取函数会请求填充一个大缓冲区,然后从该缓冲区返回数据。
您说得对,将缓冲流包装在其他缓冲流中是没有意义的:充其量它什么也做不了,最坏的情况是它增加了开销,因为数据不必要地从一个缓冲区复制到下一个缓冲区。离数据源最近的缓冲区最重要。
另一方面,API 规范中没有规定 FileWriter 和 FileReader 有缓冲区。事实上,它推荐你wrap FileWriter within a BufferedWriter和FileReader within a BufferedReader:
为了获得最高效率,请考虑将OutputStreamWriter 包装在BufferedWriter 中,以避免频繁的转换器调用。例如:
Writer out
= new BufferedWriter(new OutputStreamWriter(System.out));
(FileWriter 是 OutputStreamWriter 的子类)
这在内部是如何工作的?
如果你看一下 FileWriter 是如何实现的,故事会变得复杂,因为 FileWriter 确实 涉及缓冲区。一些细节可能取决于您使用的 Java 版本。在 OpenJDK 中,当您创建一个装饰 FileWriter 的 BufferedWriter 时:
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("file.txt"));
您正在创建一堆对象,如下所示,其中一个对象包装下一个对象:
BufferedWriter -> FileWriter -> StreamEncoder -> FileOutputStream
其中StreamEncoder 是一个内部类,是OutputStreamWriter 实现方式的一部分。
现在,当您将字符写入BufferedWriter 实例时,它首先将它们累积到 BufferedWriter 自己的缓冲区中。内部FileWriter 不会看到任何数据,直到您写入足够的数据来填充此缓冲区(或调用flush())。
当BufferedWriter 缓冲区已满时,它会通过一次调用write(char[],int,int) 将缓冲区的内容写入FileWriter。这种大数据块的传输是效率的来源:现在 FileWriter 有大块数据可以写入文件,而不是单个字符。
然后有点复杂:必须将字符转换为字节,以便将它们写入文件。这是 FileWriter 将这些数据传递给 StreamEncoder 的地方。
StreamEncoder 类使用CharsetEncoder 将字符块一次性转换为字节,并将字节累积到自己的缓冲区中。完成后,它将字节作为一个块写入最里面的 FileOutputStream。 FileOutputStream 然后调用操作系统函数来写入一个实际的文件。
如果不使用 BufferedWriter 会怎样?
如果您直接将字符写入 FileWriter,它们将被传递到 StreamEncoder 对象,该对象将它们转换为字节并存储在其私有缓冲区中,而不是直接写入 FileOutputStream。这样,FileWriter 的内部实现为您提供了一些缓冲的好处。但这不是 API 规范的一部分,因此您不应该依赖它。
此外,每次调用 FileWriter.write 都会调用 CharsetEncoder 以将字符编码为字节。一次对大块字符进行编码效率更高,编写单个字符或短字符串的开销更高。