1. 概述

[BufferedReader](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/io/BufferedReader.html) 是 Java 中用于简化从字符输入流中读取文本内容的一个类。它通过内部缓冲机制提升读取效率,避免频繁的 I/O 操作。

在本篇文章中,我们将深入探讨 BufferedReader 的使用方式,并结合实际代码示例进行说明。

2. 何时使用 BufferedReader

在需要从文件、网络套接字或其他字符输入源读取文本时,BufferedReader 是一个非常实用的选择。

核心优势:通过缓冲机制减少底层 I/O 操作次数
它会一次性读取一块字符数据到缓冲区中,后续读取操作直接从缓冲区获取,直到缓冲区为空才再次访问底层流。

2.1. 包装其他 Reader

BufferedReader 是典型的装饰器模式实现 ✅,其构造函数需要传入一个 Reader 实例:

BufferedReader reader = 
  new BufferedReader(new FileReader("src/main/resources/input.txt"));

这样我们就可以给任意 Reader 添加缓冲功能。如果不需要缓冲,也可以直接使用 FileReader

FileReader reader = 
  new FileReader("src/main/resources/input.txt");

BufferedReader 还提供了额外的便利方法,比如按行读取文本,这使得它在很多场景下更受欢迎。

2.2. 缓冲任意输入流

BufferedReader 可以包装任意的字符输入流。比如我们可以结合 InputStreamReader 来读取标准输入:

BufferedReader reader = 
  new BufferedReader(new InputStreamReader(System.in));

这个用法适用于从键盘输入、Socket 输入流、文件等任何字符输入源读取数据。

2.3. BufferedReader vs Scanner

虽然 Scanner 也能完成类似任务,但两者在功能和使用场景上存在明显差异:

特性 BufferedReader Scanner
线程安全 ✅ 是 ❌ 否
解析能力 ❌ 仅读取 ✅ 支持正则匹配解析
缓冲大小 ✅ 可自定义 ❌ 固定
默认缓冲大小 ✅ 更大 ❌ 较小
IOException 处理 ✅ 强制处理 ❌ 隐藏
性能 ✅ 更快(仅读取) ❌ 略慢(需解析)

📌 总结:

  • 如果只是逐行读取文本,推荐使用 BufferedReader
  • 如果需要解析 token、数字等结构化数据,Scanner 更合适

3. 使用 BufferedReader 读取文本

下面我们将完整演示如何初始化、使用并关闭 BufferedReader

3.1. 初始化 BufferedReader

使用构造函数包装 FileReader

BufferedReader reader = 
  new BufferedReader(new FileReader("src/main/resources/input.txt"));

默认缓冲区大小为 8KB,如果需要自定义:

BufferedReader reader = 
  new BufferedReader(new FileReader("src/main/resources/input.txt"), 16384);

还可以使用 Files 工具类更简洁地创建:

BufferedReader reader = 
  Files.newBufferedReader(Paths.get("src/main/resources/input.txt"));

这种方式避免了手动创建 FileReader,推荐用于文件读取场景。

3.2. 按行读取

使用 readLine() 方法逐行读取:

public String readAllLines(BufferedReader reader) throws IOException {
    StringBuilder content = new StringBuilder();
    String line;
    
    while ((line = reader.readLine()) != null) {
        content.append(line);
        content.append(System.lineSeparator());
    }

    return content.toString();
}

Java 8 引入了 lines() 方法,可以用 Stream API 简化写法:

public String readAllLinesWithStream(BufferedReader reader) {
    return reader.lines()
      .collect(Collectors.joining(System.lineSeparator()));
}

3.3. 关闭资源

使用完毕后必须调用 close() 释放资源,推荐使用 try-with-resources:

try (BufferedReader reader = 
       new BufferedReader(new FileReader("src/main/resources/input.txt"))) {
    return readAllLines(reader);
}

4. 其他实用方法

除了按行读取,BufferedReader 还提供了多个实用方法。

4.1. 读取单个字符

使用 read() 方法可逐字符读取:

public String readAllCharsOneByOne(BufferedReader reader) throws IOException {
    StringBuilder content = new StringBuilder();
        
    int value;
    while ((value = reader.read()) != -1) {
        content.append((char) value);
    }
        
    return content.toString();
}

该方法返回字符的 ASCII 值,当返回 -1 时表示已到流末尾。

4.2. 批量读取字符

使用 read(char[], int, int) 方法批量读取字符:

public String readMultipleChars(BufferedReader reader) throws IOException {
    int length = 5;
    char[] chars = new char[length];
    int charsRead = reader.read(chars, 0, length);

    String result;
    if (charsRead != -1) {
        result = new String(chars, 0, charsRead);
    } else {
        result = "";
    }

    return result;
}

读取最多 5 个字符,如果读不到则返回空字符串。

4.3. 跳过字符

使用 skip(long) 方法跳过指定字符数:

@Test
public void givenBufferedReader_whensSkipChars_thenOk() throws IOException {
    StringBuilder result = new StringBuilder();

    try (BufferedReader reader = 
           new BufferedReader(new StringReader("1__2__3__4__5"))) {
        int value;
        while ((value = reader.read()) != -1) {
            result.append((char) value);
            reader.skip(2L);
        }
    }

    assertEquals("12345", result);
}

上面的例子中,我们跳过了两个下划线 _,只保留数字。

4.4. mark 与 reset

使用 mark(int)reset() 可以标记当前位置并回退:

@Test
public void givenBufferedReader_whenSkipsWhitespacesAtBeginning_thenOk() 
  throws IOException {
    String result;

    try (BufferedReader reader = 
           new BufferedReader(new StringReader("    Lorem ipsum dolor sit amet."))) {
        do {
            reader.mark(1);
        } while(Character.isWhitespace(reader.read()));

        reader.reset();
        result = reader.readLine();
    }

    assertEquals("Lorem ipsum dolor sit amet.", result);
}

📌 注意:

  • mark(1) 表示只保留一个字符的回退能力
  • BufferedReadermarkSupported() 总是返回 true,所以无需额外判断

虽然 mark/reset 使用频率不高,但在需要“预读”或“回退”逻辑时非常有用。

5. 小结

本文通过多个示例演示了 BufferedReader 的常见用法,包括初始化、读取文本、关闭资源以及各种辅助方法。在需要高效读取字符流的场景中,BufferedReader 是首选工具类。

📌 关键要点回顾:

  • ✅ 推荐使用 try-with-resources 自动关闭资源
  • ✅ 按行读取首选 readLine()lines()
  • ✅ 自定义缓冲区大小可提升性能(建议使用 2 的幂)
  • mark/reset 在需要回退时非常实用

完整示例代码可参考:GitHub 项目地址


原始标题:Guide to BufferedReader