Java 的 BufferedReader
是字符输入流的缓冲装饰类,通过内置缓冲区减少底层 IO 操作次数,显著提升文本读取效率。本文基于 JDK 1.8 源码,从类结构、核心机制、关键方法到设计模式,全面解析其实现逻辑。
一、类结构与核心成员
BufferedReader
继承自抽象类 Reader
,实现了字符输入流的缓冲功能。其核心成员变量如下:
java
public class BufferedReader extends Reader {
private Reader in; // 被装饰的底层字符输入流
private char cb[]; // 字符缓冲区(默认大小 8192)
private int nChars; // 缓冲区中有效字符数
private int nextChar; // 下一个待读取字符的位置
private int markedChar; // 标记位置(用于 mark/reset)
private int readAheadLimit; // 标记后允许读取的最大字符数
private boolean skipLF; // 是否跳过下一个换行符(处理 \r\n 场景)
// ... 其他静态常量与方法 ...
}
in
:被包装的底层字符流(如FileReader
),所有 IO 操作最终委托给它。cb
:缓冲区数组,存储从底层流预读取的字符,减少read()
调用次数。markedChar
与readAheadLimit
:支持标记(mark()
)和重置(reset()
)功能,允许回退到标记位置重新读取。
二、构造函数:灵活的缓冲初始化
BufferedReader
提供两个构造函数,支持自定义缓冲区大小或使用默认值(8192 字符):
java
public BufferedReader(Reader in, int sz) {
super(in); // 调用父类 Reader 的构造函数(初始化锁对象)
if (sz <= 0) throw new IllegalArgumentException("Buffer size <= 0");
this.in = in;
cb = new char[sz]; // 初始化指定大小的缓冲区
nextChar = nChars = 0; // 初始无有效字符
}
public BufferedReader(Reader in) {
this(in, defaultCharBufferSize); // 默认缓冲区大小 8192
}
设计要点 :
通过装饰器模式包装底层 Reader
,将用户与底层流的直接交互隔离,用户只需操作 BufferedReader
即可获得缓冲能力。
三、核心机制:缓冲区填充与读取优化
BufferedReader
的核心逻辑围绕"预读取-缓冲-按需消费"展开,关键方法是 fill()
(填充缓冲区)和 read()
(从缓冲区读取)。
1. fill()
:缓冲区填充策略
当缓冲区数据不足(nextChar >= nChars
)时,fill()
负责从底层流读取数据填充缓冲区。若存在有效标记(markedChar
),需考虑标记位置的保留:
java
private void fill() throws IOException {
if (markedChar <= UNMARKED) { // 无标记:直接覆盖缓冲区
dst = 0;
} else {
int delta = nextChar - markedChar;
if (delta >= readAheadLimit) { // 超过标记允许的读取范围:失效标记
markedChar = INVALIDATED;
dst = 0;
} else {
// 标记有效:调整缓冲区,保留标记位置到当前位置的数据
System.arraycopy(cb, markedChar, cb, 0, delta);
markedChar = 0;
dst = delta;
}
}
// 从底层流读取数据到缓冲区(dst 为填充起始位置)
int n = in.read(cb, dst, cb.length - dst);
if (n > 0) {
nChars = dst + n; // 更新有效字符数
nextChar = dst; // 重置下一个读取位置
}
}
关键逻辑:
- 无标记时,缓冲区从头开始填充;
- 有标记时,若未超出
readAheadLimit
,则将标记位置后的数据前移,保留上下文; - 最终调用底层流的
read()
填充缓冲区剩余空间。
2. read()
:单字符读取
read()
方法从缓冲区读取单个字符,若缓冲区空则调用 fill()
填充:
java
public int read() throws IOException {
synchronized (lock) { // 同步保证线程安全(继承自 Reader 的 lock 对象)
ensureOpen(); // 检查流是否关闭
for (;;) {
if (nextChar >= nChars) { // 缓冲区空,填充
fill();
if (nextChar >= nChars) return -1; // 填充后仍空:EOF
}
if (skipLF) { // 跳过前一个 \r 后的 \n
skipLF = false;
if (cb[nextChar] == '\n') {
nextChar++;
continue;
}
}
return cb[nextChar++]; // 返回当前字符并移动指针
}
}
}
优化点 :
通过 skipLF
状态处理 \r\n
换行符(如 Windows 系统),避免重复读取换行符。
3. readLine()
:行读取的核心实现
readLine()
是最常用的方法之一,用于读取一行文本(以 \n
、\r
或 \r\n
结尾):
java
public String readLine() throws IOException {
return readLine(false); // 调用重载方法,不跳过初始换行符
}
String readLine(boolean ignoreLF) throws IOException {
StringBuffer s = null;
int startChar;
synchronized (lock) {
ensureOpen();
boolean omitLF = ignoreLF || skipLF; // 是否跳过初始换行符
bufferLoop:
for (;;) {
if (nextChar >= nChars) fill(); // 缓冲区空则填充
if (nextChar >= nChars) return (s != null && s.length() > 0) ? s.toString() : null; // EOF
boolean eol = false; // 是否遇到行终止符
int i;
// 查找行终止符(\n 或 \r)
for (i = nextChar; i < nChars; i++) {
if (cb[i] == '\n' || cb[i] == '\r') {
eol = true;
break;
}
}
startChar = nextChar;
nextChar = i; // 移动到行终止符位置
if (eol) { // 处理行终止符
String str = (s == null)
? new String(cb, startChar, i - startChar) // 直接截取缓冲区内容
: s.append(cb, startChar, i - startChar).toString(); // 拼接已有内容
nextChar++; // 跳过当前终止符
if (cb[i] == '\r') skipLF = true; // 标记下一个 \n 需跳过(处理 \r\n)
return str;
}
// 未找到终止符:将缓冲区内容追加到 StringBuffer
if (s == null) s = new StringBuffer(defaultExpectedLineLength); // 默认行长度 80
s.append(cb, startChar, i - startChar);
}
}
}
关键逻辑:
- 循环读取缓冲区,直到找到行终止符或缓冲区耗尽;
- 使用
StringBuffer
拼接跨缓冲区的行内容(如一行数据分多次填充到缓冲区); - 处理
\r\n
组合换行符(通过skipLF
标记跳过后续的\n
)。
四、标记与重置:mark()
与 reset()
BufferedReader
支持标记当前位置(mark()
),并通过 reset()
回退到标记位置重新读取,这依赖缓冲区对标记位置的保留:
java
public void mark(int readAheadLimit) throws IOException {
if (readAheadLimit < 0) throw new IllegalArgumentException("Read-ahead limit < 0");
synchronized (lock) {
ensureOpen();
this.readAheadLimit = readAheadLimit; // 标记后允许读取的最大字符数
markedChar = nextChar; // 记录当前读取位置
markedSkipLF = skipLF; // 记录当前换行符跳过状态
}
}
public void reset() throws IOException {
synchronized (lock) {
ensureOpen();
if (markedChar < 0) throw new IOException(markedChar == INVALIDATED ? "Mark invalid" : "Stream not marked");
nextChar = markedChar; // 回退到标记位置
skipLF = markedSkipLF; // 恢复换行符跳过状态
}
}
设计限制:
readAheadLimit
需合理设置:若超过缓冲区大小,会触发缓冲区扩容(fill()
中重新分配数组);- 若标记后读取的字符数超过
readAheadLimit
,标记失效(markedChar
设为INVALIDATED
)。
五、设计模式分析
BufferedReader
的源码中体现了多种经典设计模式,是理解 Java IO 框架的关键。
1. 装饰器模式(Decorator Pattern)
BufferedReader
是装饰器模式的典型应用:
- 核心角色 :
Reader
是抽象组件(Component
),BufferedReader
是具体装饰器(Concrete Decorator
); - 功能增强 :通过包装底层
Reader
(如FileReader
),为其添加缓冲功能,用户无需修改原Reader
代码即可获得性能提升。
java
// 装饰器模式示例:将 FileReader 包装为 BufferedReader
Reader fileReader = new FileReader("test.txt");
BufferedReader bufferedReader = new BufferedReader(fileReader); // 增强缓冲能力
2. 模板方法模式(Template Method)
Reader
作为抽象基类,定义了 read()
、close()
等模板方法,子类(如 BufferedReader
、FileReader
)实现具体逻辑:
- 统一接口 :用户只需调用
read()
,无需关心底层是缓冲流还是原始流; - 扩展灵活 :新增字符流类型(如
StringReader
)时,只需继承Reader
并实现抽象方法。
3. 迭代器模式(Iterator Pattern)(Java 8+)
Java 8 的 lines()
方法返回 Stream<String>
,内部通过迭代器懒加载行数据:
java
public Stream<String> lines() {
Iterator<String> iter = new Iterator<String>() {
String nextLine = null;
public boolean hasNext() { // 懒加载:仅在需要时读取下一行
try { return (nextLine != null) || ((nextLine = readLine()) != null); }
catch (IOException e) { throw new UncheckedIOException(e); }
}
public String next() { /* 实现略 */ }
};
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iter, ...), false);
}
优势 :通过迭代器将行读取转换为流式处理,支持 filter()
、map()
等函数式操作,符合 Java 8 函数式编程趋势。
六、总结
BufferedReader
是 Java IO 体系中"缓冲优化"的经典实现,其核心价值在于通过预读取和缓冲区管理,将多次底层 IO 操作合并为单次,显著提升文本读取效率。结合源码分析,其设计特点可总结为:
- 缓冲机制 :通过
cb
缓冲区减少底层read()
调用次数,降低 IO 开销; - 状态管理 :
skipLF
、markedChar
等变量精细控制换行符处理和标记重置逻辑; - 模式应用:装饰器模式实现功能扩展,模板方法统一接口,迭代器模式支持流式处理;
- 线程安全 :通过
synchronized (lock)
保证多线程下的操作原子性(lock
继承自Reader
)。
理解 BufferedReader
的源码,不仅能掌握高效文本读取的底层逻辑,更能深入体会设计模式在框架中的实际应用,为高性能 IO 编程和问题排查提供坚实基础。