1. 概述

顾名思义,FileReader 是 Java 中用于读取文件内容的一个便捷工具类。它专为字符流设计,适合处理文本文件。

本文将带你掌握 Reader 的基本概念,并通过实际示例演示如何使用 FileReader 高效地从文件中读取字符数据。我们也会讨论它的局限性以及在真实项目中需要注意的“坑”。

2. Reader 基础知识

查看 FileReader 的源码你会发现,这个类本身几乎没有实现什么逻辑,方法也很少。那问题来了:真正干活的是谁?

答案就在 Java I/O 的继承体系中 —— FileReader 继承自 Reader,而 Reader 是一个抽象基类,定义了所有字符读取操作的核心行为。

Reader 提供了以下关键能力:

  • 读取单个字符
  • 读取字符数组
  • 标记和重置流中的位置
  • 跳过指定数量的字符
  • 关闭输入流

所有 Reader 的实现类都必须实现 read()close() 这两个抽象方法。大多数子类还会覆盖其他方法以提升性能或扩展功能。

2.1 什么时候该用 FileReader?

FileReader 的继承链如下:

public class InputStreamReader extends Reader {}

public class FileReader extends InputStreamReader {}

可以看到,FileReader 实际上是 InputStreamReader 的封装。而 InputStreamReader 的作用是将字节流(InputStream)转换为字符流(Reader),过程中会进行字符编码解码。

所以,FileReader 本质上就是:

new InputStreamReader(new FileInputStream(file));

只不过它帮你省去了手动组合的步骤。

⚠️ 重点来了:**FileReader 使用的是系统默认字符集(如 Windows 是 GBK,Linux/Mac 通常是 UTF-8)**。

这意味着:

  • ✅ 如果你的文件编码和系统默认一致,用 FileReader 简单粗暴,没问题。
  • ❌ 如果文件是 UTF-8 编码但运行在 GBK 系统上,就会出现乱码!

因此结论很明确:

只有当你确定使用系统默认编码读取文本文件时,才考虑 FileReader。否则,直接用 InputStreamReader 显式指定编码更安全。

你可以把它理解为“开箱即用但不够灵活”的工具,适合快速脚本或内部小工具,但不推荐在生产环境的通用模块中使用。

3. 使用 FileReader 读取文本文件

下面我们通过一个具体例子,演示如何用 FileReader 读取 HelloWorld.txt 文件内容。

3.1 创建 FileReader 实例

FileReader 提供了三个构造函数,方便不同场景下的初始化:

public FileReader(String fileName) throws FileNotFoundException {
    super(new FileInputStream(fileName));
}

public FileReader(File file) throws FileNotFoundException {
    super(new FileInputStream(file));
}

public FileReader(FileDescriptor fd) {
    super(new FileInputStream(fd));
}

常用的是前两个。比如我们已知文件路径,可以直接传文件名或 File 对象:

String path = "/tmp/HelloWorld.txt";
FileReader fileReader = new FileReader(path);

⚠️ 注意:这些构造函数可能抛出 FileNotFoundException,记得处理异常或向上抛出。

3.2 逐个读取字符

下面这个方法演示如何用 read() 方法逐个读取字符,直到文件末尾:

public static String readAllCharactersOneByOne(Reader reader) throws IOException {
    StringBuilder content = new StringBuilder();
    int nextChar;
    while ((nextChar = reader.read()) != -1) {
        content.append((char) nextChar);
    }
    return String.valueOf(content);
}

📌 关键点:

  • read() 返回的是 int,范围是 0~65535,表示字符的 Unicode 值
  • 读到末尾时返回 -1,这是循环终止条件
  • 强转 (char) 才能得到实际字符

测试一下:

@Test
public void givenFileReader_whenReadAllCharacters_thenReturnsContent() throws IOException {
    String expectedText = "Hello, World!";
    File file = new File("/tmp/HelloWorld.txt");
    try (FileReader fileReader = new FileReader(file)) {
        String content = FileReaderExample.readAllCharactersOneByOne(fileReader);
        Assert.assertEquals(expectedText, content);
    }
}

✅ 使用 try-with-resources 确保资源自动关闭,避免文件句柄泄漏。

3.3 批量读取字符数组

逐个读效率太低,更常见的做法是批量读取到字符数组中

public static String readMultipleCharacters(Reader reader, int length) throws IOException {
    char[] buffer = new char[length];
    int charactersRead = reader.read(buffer, 0, length);
    if (charactersRead != -1) {
        return new String(buffer, 0, charactersRead);
    } else {
        return "";
    }
}

📌 与单字符读取的区别:

  • 方法签名:read(char[] cbuf, int off, int len)
  • 返回值:成功时返回实际读取的字符数,不是字符本身
  • 到达文件末尾仍返回 -1

测试代码:

@Test
public void givenFileReader_whenReadMultipleCharacters_thenReturnsContent() throws IOException {
    String expectedText = "Hello";
    File file = new File("/tmp/HelloWorld.txt");
    try (FileReader fileReader = new FileReader(file)) {
        String content = FileReaderExample.readMultipleCharacters(fileReader, 5);
        Assert.assertEquals(expectedText, content);
    }
}

4. 局限性与最佳实践

虽然 FileReader 使用简单,但在实际开发中存在明显短板:

❌ 主要限制

  1. 无法指定字符集
    强依赖系统默认编码,跨平台易出乱码。

  2. 无缓冲机制
    每次 read() 都可能触发一次系统调用,I/O 性能差。

✅ 推荐做法

始终配合 BufferedReader 使用

FileReader fileReader = new FileReader("/tmp/data.txt");
BufferedReader bufferedReader = new BufferedReader(fileReader);

或者更简洁:

BufferedReader reader = new BufferedReader(new FileReader("/tmp/data.txt"));

这样就能利用缓冲区减少 I/O 次数,大幅提升性能。

✅ **需要指定编码时,直接使用 InputStreamReader + FileInputStream**:

InputStream inputStream = new FileInputStream("/tmp/data.txt");
InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
BufferedReader bufferedReader = new BufferedReader(reader);

这才是处理文本文件的“标准姿势”,尤其适用于读取日志、配置文件、CSV 等可能涉及多种编码的场景。

5. 总结

FileReader 是一个轻量级工具类,适合在已知文件编码与系统一致的简单场景下快速读取文本。

但它的硬伤也很明显:不支持自定义编码、无缓冲、跨平台风险高。因此:

⚠️ 不建议在生产环境的核心模块中直接使用 FileReader

✅ 正确做法是:

  • 需要缓冲 → 包一层 BufferedReader
  • 需要指定编码 → 改用 InputStreamReader
  • 两者结合 → new BufferedReader(new InputStreamReader(inputStream, charset))

这才是 Java 文本读取的黄金组合。


📌 完整示例代码已托管至 GitHub:https://github.com/baeldung/core-java-modules/tree/master/core-java-io-apis


原始标题:A Guide to the Java FileReader Class | Baeldung