开篇
在设计模式中有一种叫做装饰者模式,刚好BufferedReader的源码是这个设计模式的最好例子,一并看下源码。
源码分析
构造函数
- BufferedReader的类变量的Reader in 用以构造函数参数中的Reader in参数,BufferedReader的所有读写操作都通过Reader对象进行操作。
- BufferedReader相当于针对内部的Reader对象进行了一层包装,可以理解为装饰者。
public class BufferedReader extends Reader {
private Reader in;
private char cb[];
//nextChar代表下次要读取的位置,nChars表示总共的字符个数
private int nChars, nextChar;
private static final int INVALIDATED = -2;
private static final int UNMARKED = -1;
private int markedChar = UNMARKED;
private int readAheadLimit = 0; /* Valid only when markedChar > 0 */
private boolean skipLF = false;
private boolean markedSkipLF = false;
private static int defaultCharBufferSize = 8192;
private static int defaultExpectedLineLength = 80;
public BufferedReader(Reader in, int sz) {
super(in);
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);
}
}
加载数据
- 负责通过Reader in对象读取字符到指定数量的字符数据到cb数组当中。
- dst表示保存数据的起始位置,cb.length-dst表示读取字符的个数。
- 在执行read和readline操作的之前如果cb当中可读字符不足会先执行fill()读取字符。
private void fill() throws IOException {
int dst;
if (markedChar <= UNMARKED) {
/* No mark */
dst = 0;
} else {
// 省略一部分代码
}
int n;
do {
// 从底层input读取数据到cb,cb中起始位置是dst,
// 读取的长度是cb的lenght减去起始位置dst,理解剩余能够装的字符
n = in.read(cb, dst, cb.length - dst);
} while (n == 0);
if (n > 0) {
// 设置最大可读字符结束位置
nChars = dst + n;
// 设置可读字符的起始位置
nextChar = dst;
}
}
read过程
- 读取过程中如果满足条件(nextChar 表示下一个读取字符>= nChars可用字符),代表字符已经读取完毕那么就通过fill()进行预加载。
- 读取当前字符并累加当前可读取字符,执行nextChar++操作。
public int read() throws IOException {
synchronized (lock) {
ensureOpen();
for (;;) {
if (nextChar >= nChars) {
fill();
if (nextChar >= nChars)
return -1;
}
if (skipLF) {
skipLF = false;
if (cb[nextChar] == '\n') {
nextChar++;
continue;
}
}
// 读取当前字符并累加下一个读取位置
return cb[nextChar++];
}
}
}
readline过程
- 读取过程中如果满足条件(nextChar 表示下一个读取字符>= nChars可用字符),代表字符已经读取完毕那么就通过fill()进行预加载。
- 读取过程中如果遇到\r\n则中断循环,通过str = new String(cb, startChar, i - startChar)返回整行数据。
String readLine(boolean ignoreLF) throws IOException {
//读取的数据最终放在这个s中,
StringBuffer s = null;
int startChar;
synchronized (lock) {
ensureOpen();
boolean omitLF = ignoreLF || skipLF;
bufferLoop:
for (;;) {
if (nextChar >= nChars)
fill();
if (nextChar >= nChars) { /* EOF */
if (s != null && s.length() > 0)
//从这里返回,可能是因为读取的数据最后没有以\n或\r结束
return s.toString();
else
// 从这里返回,是因为开始读的时候,就已经是input的末尾了,
// 所以s本身就没有被初始化,只能返回null
return null;
}
boolean eol = false;
char c = 0;
int i;
/* Skip a leftover '\n', if necessary */
if (omitLF && (cb[nextChar] == '\n'))
nextChar++;
skipLF = false;
omitLF = false;
charLoop:
for (i = nextChar; i < nChars; i++) {
c = cb[i];
if ((c == '\n') || (c == '\r')) {
eol = true;
break charLoop;
}
}
startChar = nextChar;
nextChar = i;
if (eol) {
String str;
// 运行到这,s为null,说明是第一次循环中就读到了行尾。
if (s == null) {
str = new String(cb, startChar, i - startChar);
} else {
// 运行到这,起码说明是第二次循环了,s里已经有了第一次读取的数据
s.append(cb, startChar, i - startChar);
str = s.toString();
}
nextChar++;
if (c == '\r') {
skipLF = true;
}
// 运行到这说明读到了行尾,返回str
return str;
}
if (s == null)
s = new StringBuffer(defaultExpectedLineLength);
// 运行到这说明,读取了整个cb的数据,发现一直没有\n或者\r,
// 之后回到最初循环继续读取。
s.append(cb, startChar, i - startChar);
}
}
}
类依赖图
-
BufferedReader的类依赖图如下图,所有的io reader都是继承自Reader作为基类。
装饰设计模式
装饰设计模式:javaIO技术中的装饰设计模式,对一组对象的功能进行增强时,就可以使用该设计模式