这是一个非常基础且重要的 I/O 类,理解它对于进行高效的文件读取和网络数据读取至关重要。

什么是 BufferedReader?
BufferedReader 是 Java I/O 包 (java.io) 中的一个类,它的主要作用是为字符输入流提供一个缓冲区。
它就像一个“蓄水池”:
- 没有缓冲区:你需要一杯水,就直接去水龙头接一杯,频繁地去接水龙头开关,效率很低。
- 有缓冲区:你先打开水龙头,把水蓄满一个水池,之后你需要水,就直接从水池里舀,非常快,水池满了之后,再一次性去打开水龙头蓄水。
BufferedReader 就是这个“水池”,它内部维护了一个字符数组(即缓冲区),当读取数据时,它会一次性从底层输入流(如 FileReader)中读取一大块数据到缓冲区中,之后,程序每次调用 read() 方法时,都是从内存中的这个缓冲区读取,而不是直接访问慢速的磁盘或网络,从而极大地提高了读取效率。
核心优点:为什么使用它?
性能提升:这是 BufferedReader 最核心的优点,I/O 操作(尤其是磁盘和网络 I/O)是非常耗时的,通过减少直接进行 I/O 操作的次数,BufferedReader 能显著提高程序的性能。

示例对比: 假设你要从一个文件中读取 1000 个字符。
- 不使用
BufferedReader:FileReader可能会进行 1000 次磁盘读取操作(每次读一个字符)。 - 使用
BufferedReader:BufferedReader会一次性从FileReader中读取 8192 个字符(默认缓冲区大小)到内存,你的程序可以从内存中连续读取 1000 个字符,而FileReader的磁盘读取操作可能只有 1 次。
如何使用 BufferedReader?
BufferedReader 本身不能直接读取文件或网络数据,它必须包装一个底层字符输入流。
基本构造方法
BufferedReader(Reader in):创建一个默认缓冲区大小(通常是 8192 字节)的缓冲字符输入流。BufferedReader(Reader in, int sz):创建一个指定缓冲区大小的缓冲字符输入流。
常用方法
| 方法 | 描述 |
|---|---|
int read() |
读取单个字符,返回读取的字符(0-65535 的 int 值),如果已到达流末尾,则返回 -1。 |
int read(char[] cbuf, int off, int len) |
将字符读入一个数组的一部分,这是最常用的读取方法之一。 |
String readLine() |
读取一行文本,该方法会读取直到遇到换行符 \n、回车符 \r 或两者组合 \r\n,返回读取的字符串(不包含换行符),如果已到达流末尾,则返回 null。这是 BufferedReader 最著名、最常用的方法。 |
long skip(long n) |
跳过 n 个字符。 |
boolean ready() |
判断此流是否已准备好被读取(即缓冲区中是否有数据)。 |
void close() |
关闭流,并释放与该流关联的所有系统资源。非常重要! |
代码示例
示例 1:从文件中逐行读取
这是 BufferedReader 最经典、最常见的用法,我们需要结合 FileReader 来使用。
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class BufferedReaderExample {
public static void main(String[] args) {
// 使用 try-with-resources 语句,可以自动关闭资源,避免资源泄漏
// 这是 Java 7 及以上版本推荐的最佳实践
try (BufferedReader reader = new BufferedReader(new FileReader("my_file.txt"))) {
String line;
// readLine() 在读取到文件末尾时会返回 null
// 所以我们可以用 while (line != null) 来循环
while ((line = reader.readLine()) != null) {
// 处理每一行内容
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
代码解析:

new FileReader("my_file.txt"):创建一个FileReader,它是底层流,负责直接从文件中读取字节并将其解码为字符。new BufferedReader(...):将FileReader包装起来,创建一个带缓冲区的BufferedReader。try-with-resources: 中的资源会在try代码块执行完毕后自动调用close()方法,即使发生异常也能确保关闭,是现代 Java 编程的标准做法。reader.readLine():循环调用此方法,每次读取文件的一行,直到文件末尾返回null。
示例 2:从控制台读取用户输入
System.in 是一个 InputStream(字节流),我们需要用 InputStreamReader 将其转换为 Reader(字符流),然后再用 BufferedReader 包装。
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class ConsoleInputExample {
public static void main(String[] args) {
// System.in 是 InputStream
// InputStreamReader 将 InputStream 转换为 Reader
// BufferedReader 包装 Reader 以提供缓冲和 readLine() 方法
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
System.out.println("请输入您的名字,输入 'exit' 退出:");
try {
String userInput;
while ((userInput = reader.readLine()) != null) {
if ("exit".equalsIgnoreCase(userInput)) {
System.out.println("程序退出。");
break;
}
System.out.println("你好, " + userInput + "!");
System.out.println("请继续输入: ");
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 一定要手动关闭,因为 System.in 是系统资源
try {
if (reader != null) {
reader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
重要注意事项
必须关闭流
BufferedReader 底层会持有系统资源(如文件句柄、网络连接),如果不关闭这些资源,会导致资源泄漏,最终可能导致程序崩溃(打开的文件数量达到系统上限)。
最佳实践:始终使用 try-with-resources 语句,它可以自动处理资源的关闭。
readLine() 的一个“陷阱”
readLine() 方法在读取一行后,不会将行终止符(\n 或 \r\n)包含在返回的字符串中,这是一个常见的设计,但也需要注意。
BufferedReader vs Scanner
很多初学者会混淆 BufferedReader 和 Scanner,它们都可以用来读取输入,但有显著区别:
| 特性 | BufferedReader |
Scanner |
|---|---|---|
| 底层 | 基于 Reader(字符流) |
基于 InputStream(字节流) |
| 性能 | 非常快,专为高效读取设计 | 较慢,因为它有更复杂的解析逻辑(如正则表达式) |
| 主要功能 | 高效读取文本行 | 解析原始类型和字符串(如 nextInt(), nextDouble()) |
| 线程安全 | 是 | 否 |
| 分隔符 | 固定为换行符 \n, \r, \r\n |
可自定义(默认是空白字符:空格、Tab、换行等) |
选择建议:
- 如果只是高效地逐行读取文本(如文件、控制台),首选
BufferedReader。 - 如果需要从输入中解析出数字、日期等原始类型,或者需要灵活的分隔符,
Scanner更方便。
BufferedReader 是 Java 开发者工具箱中一个不可或缺的工具,它的核心价值在于通过缓冲机制来提升 I/O 性能。
记住以下几点:
- 它是一个包装器:必须配合
Reader(如FileReader,InputStreamReader)使用。 readLine()是王牌:这是它最常用、最方便的方法。- 性能为王:在处理大文件或需要频繁读取的场景下,性能优势明显。
- 资源管理要牢记:使用
try-with-resources来确保流被正确关闭。 - 与
Scanner区分:需要解析原始类型用Scanner,需要高效读取纯文本用BufferedReader。
