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
使用简单,但在实际开发中存在明显短板:
❌ 主要限制
无法指定字符集
强依赖系统默认编码,跨平台易出乱码。无缓冲机制
每次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