CodeGym /课程 /JAVA 25 SELF /BufferedReader, BufferedWriter:缓冲与优势

BufferedReader, BufferedWriter:缓冲与优势

JAVA 25 SELF
第 36 级 , 课程 0
可用

1. 引言

回顾一下 FileReaderFileWriter 是如何工作的。每次调用它们的 read()write() 方法,都会访问文件系统——也就是说,计算机会真实地去磁盘上读取或写入一个字符。如果文件很大,而你一次只读/写一个字符,这就会变成成千上万、甚至上百万次磁盘访问的“马拉松”。而众所周知,磁盘并不是程序员最快的朋友。

可以这样想象:你要把一桶水倒进瓶子里,却用的是茶匙。形式上可行,但非常慢。更明智的做法是用勺或杯子。处理文件亦然:逐字符读写,就像用茶匙搬水。

如下所示:

// 按字符读取文件(就像用 "茶匙"!)
try (FileReader reader = new FileReader("big.txt")) {
    int c;
    while ((c = reader.read()) != -1) {
        // 处理字符(例如,仅统计数量)
    }
}

如果文件很大,你会注意到程序运行非常缓慢。

缓冲:是什么以及为什么需要

缓冲区是内存中的一块特殊区域(通常是数组),数据会被成块地读入或写出(例如每次 8 KB 或更多),而不是逐字符处理。简单说,缓冲区就是一块中间存储的内存,相当于“中间水桶”,以更大的批次读写数据。

工作流程是这样的:读取时,程序一次访问磁盘,把整块数据装入缓冲区,然后再从内存中按需把字符一个个“发”给你。一旦这一块用完,就再加载下一块。写入时也一样:数据先放入缓冲区,然后再成块写入磁盘(或者在你调用 flush() 时立即写出)。

为什么更快?因为磁盘本身就慢,尤其是被频繁“小打小闹”时。而减少访问次数、每次多拿一些,会快得多。简而言之:缓冲能减少对磁盘的访问次数,从而加速程序。

2. BufferedReader 和 BufferedWriter:语法与示例

在 Java 中,用于对文本文件读写进行缓冲的两个类是:

  • BufferedReader — 用于读取文本文件。
  • BufferedWriter — 用于写入文本文件。

它们构建在普通的 Reader/Writer(如 FileReader/FileWriter)之上,增加了缓冲能力。

使用 BufferedReader 按行读取文件

最常见的场景是按行读取。方法 readLine() 会返回直到换行符为止的一整行("\n""\r\n")。

import java.io.*;

public class BufferedReaderExample {
    public static void main(String[] args) {
        try (BufferedReader reader = new BufferedReader(new FileReader("input.txt"))) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line); // 将该行输出到屏幕
            }
        } catch (IOException e) {
            System.out.println("读取文件时出错:" + e.getMessage());
        }
    }
}

这里发生了什么?

我们创建了一个 FileReader,它能逐字符读取文件,然后把它包在 BufferedReader 中。缓冲读取器不是逐字符地拿数据,而是成块拿到内存,再通过 readLine() 按行“分发”给你。最终你只需要写一个 while 循环,逐行获取字符串,不必关心文件有多大:读取依然会高效而节省开销。

使用 BufferedWriter 写入文件

把字符串写入文件同样可以很高效:

import java.io.*;

public class BufferedWriterExample {
    public static void main(String[] args) {
        try (BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))) {
            writer.write("Hello, world!");
            writer.newLine(); // 换行(取决于 OS)
            writer.write("这是第二行。");
        } catch (IOException e) {
            System.out.println("写入文件时出错:" + e.getMessage());
        }
    }
}

这里发生了什么?

我们先创建 FileWriter,再把它包在 BufferedWriter 中。当你调用 write()newLine() 时,数据不会立刻写入磁盘,而是先放到缓冲区(内存中的一层“垫片”)。只有当缓冲区被写满、你关闭流(或显式调用 flush())时,累积的文本才会成批写入文件。这种方式显著加快写入速度并减少磁盘访问。

在一个完整应用中是什么样?

假设我们编写一个简单的“日记”程序,把输入保存到文件并在屏幕上显示出来。

import java.io.*;
import java.util.Scanner;

public class DiaryApp {
    public static void main(String[] args) {
        String fileName = "diary.txt";
        Scanner scanner = new Scanner(System.in);

        // 写入新记录
        System.out.print("请输入一条新的日记记录:");
        String entry = scanner.nextLine();

        try (BufferedWriter writer = new BufferedWriter(new FileWriter(fileName, true))) {
            writer.write(entry);
            writer.newLine();
            System.out.println("记录已保存!");
        } catch (IOException e) {
            System.out.println("写入时出错:" + e.getMessage());
        }

        // 读取所有记录
        System.out.println("\n您的日记:");
        try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            System.out.println("读取时出错:" + e.getMessage());
        }
    }
}

我们使用文件名 "diary.txt",提示用户输入一条新记录。为保存数据,我们以 append 模式使用 FileWriter(传入 true),因此旧的内容不会被覆盖——每条新记录都会整齐地追加到文件末尾。再包上一层 BufferedWriter,写入就会更快更省 I/O:数据先在内存中累积,然后成块写入磁盘。

随后我们通过 BufferedReader 打开同一个文件进行读取。它会成块获取内容,再一行一行地交给你。循环中程序只是把每一行打印到屏幕上,最终你就能从头到尾看到自己的“日记”。

3. BufferedReader 和 BufferedWriter 的优势

显著加速

使用带缓冲的流进行读写时,程序对磁盘的访问次数会减少几十倍,甚至上百倍。这在大文件上尤其明显。

便捷的方法

  • BufferedReader.readLine() — 支持按行读取,处理文本文件(如日志、CSV、配置)非常方便。
  • BufferedWriter.newLine() — 添加换行,并会根据操作系统选择合适的换行符。

易于使用

  • 可以与其他流轻松组合(例如,将 InputStreamReader 包在 BufferedReader 中,以读取不同编码的文件)。
  • 配合 try-with-resources 使用可自动关闭所有资源。

灵活

  • 如果默认的缓冲区大小(通常为 8 KB)不合适,你可以显式指定:
BufferedReader reader = new BufferedReader(new FileReader("big.txt"), 16384); // 16 KB 缓冲区

4. 何时使用 BufferedReader 和 BufferedWriter

建议使用:

  • 当处理文本文件(日志、CSV、大量文本数据)。
  • 当需要按行读取或写入文件。
  • 当处理大文件且性能重要时,以加速 I/O。
  • 当需要处理来自网络或其他支持 Reader/Writer 的数据源时。

不建议使用:

  • 处理二进制文件(如图片、压缩包、视频)——应使用 InputStream/OutputStream
  • 如果文件非常小且只读/写一次完整内容——缓冲带来的收益很小(但通常也无害)。

5. 实用细节

与不同字符编码配合

如果需要以特定编码读/写文件(例如 "UTF-8""Windows-1251"),请将 InputStreamReader/OutputStreamWriter 与带缓冲的流组合使用:

BufferedReader reader = new BufferedReader(
    new InputStreamReader(new FileInputStream("input.txt"), "UTF-8")
);

BufferedWriter writer = new BufferedWriter(
    new OutputStreamWriter(new FileOutputStream("output.txt"), "UTF-8")
);

显式刷新缓冲区

有时你需要确保数据已落盘(例如写日志或收据)。此时调用 writer.flush()。通常不必手动调用,因为关闭流会自动刷新缓冲区。

缓冲区大小

默认缓冲区大约是 8 KB。若你确信调整大小能带来性能收益(例如处理超大文件时),可以自行设置。

对比:FileReader/FileWriter 与 BufferedReader/BufferedWriter

大文件上的速度 按行读取的便利性 按行写入的便利性 编码灵活性
FileReader/FileWriter 否(只能逐字符) 否(只能逐字符) 仅默认
BufferedReader/Writer 是(readLine() 是(newLine() 是(通过 InputStreamReader/OutputStreamWriter

6. 使用 BufferedReader 和 BufferedWriter 时的常见错误

错误 1:忘记关闭流。 如果不使用 try-with-resources 或不调用 close(),文件可能保持锁定,数据也可能未写入磁盘。请务必使用 try-with-resources

错误 2:把文本与二进制的处理方式混淆。 试图通过 BufferedReader 打开二进制文件(".jpg"".zip")会得到乱码并且很可能报错。处理二进制文件应使用 InputStream/OutputStream

错误 3:在大数据量场景下不使用缓冲。 逐字符读/写会让程序很慢。处理大文件时务必使用缓冲。

错误 4:需要时却没有调用 flush() 如果需要数据立即落盘(例如用于日志),请调用 writer.flush()。不过通常关闭流就会自动刷新。

错误 5:忽略编码问题。 如果用错误的编码打开文件,文本可能显示不正确(俄文字母会变成“?”、奇怪字符等)。当编码与系统默认不同,请始终显式指定需要的编码(例如 "UTF-8")。

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION