BufferReader
1.1 测试样例
先看这样一段测试代码 ,代码的基本功能就是读文本,然后输出到控制台。以下的说明都是根据这段测试代码展开
public static void main(String[] args) {
try {
BufferedReader bufferedReader = new BufferedReader(new
FileReader("D:\\WORKSPACE\\template.txt"));
String str;
StringBuffer stringBuffer = new StringBuffer();
while ((str = bufferedReader.readLine()) != null) {
stringBuffer.append(str + System.getProperty("line.separator"));
}
System.out.println(stringBuffer.toString());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
1.2 设计模式
BufferedReader采用了装饰者模式,装饰者模式目的在于扩展被修饰者功能,通过上例的BufferReader的构造方法, BufferedReader bufferedReader = new BufferedReader(new FileReader(“D:\WORKSPACE\template.txt”)); 可以看出BufferReader是用来装饰FileReader的,增强FileReader的功能。详细见下图比较
1.3 源码解析
1.3.1 API文档翻译
使用输入流读文本时,缓存字符以便高效的读取字符,数组和文本行 缓存大小可以是自定义,也可以使用默认的,默认的大小是足够应付大多数情况了 一般情况下,都是Reader包装字节流,因此我们建议使用BufferedReader去包装字符流他的方法Read()是更有价值IDE,比如 FileReader和InputStreamReader. 比如 eg.BufferedReader in = new BufferedReader(new FileReader("foo.in")); 将缓存输入从指定的文件,没有缓存的话,每一次读Read方法和readline方法可能会造成先把文件读成字节,然后在转字符,然后返回, 这样效率将是非常低的。 使用DataInputStreams进行文本输入的程序可以通过用适当的BufferedReader替换每个DataInputStream来进行本地化。
1.3.2 代码执行流程
当执行 BufferedReader bufferedReader = new BufferedReader(new FileReader(“D:\WORKSPACE\template.txt”))时
- 给父类的lock值赋值为FileReader,用于之后的代码同步
- 给全局变量Reader in 赋值为FileReader
- 初始化缓存的全局字符数组cb,默认长度是8192,可缓存的数据大小是8192*2/1024=16kb
- 给全局变量指针nextchar赋值为0,nextchar意思是读到缓存字符数组cb到哪个位置了(读了多少个字节了),不是读到文件内文本数据到哪了。nchar是cb中具体字节大小。这部分下面会通过代码具体介绍。
BufferedReader.class
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.class
protected Reader(Object lock) {
if (lock == null) {
throw new NullPointerException();
}
this.lock = lock;
}
当执行bufferedReader.readLine()时
- 输入流将文本读入缓存变量cb[]中。默认读16Kb文本大小到此数组中,上面有介绍。
- 读缓存变量中的数据,读到了\r,\n或者\r\n,程序知道了这是一行的结尾了,此时程序就将之前读到的字符返回,readline方法执行完毕
- 如果读到末尾发现程序中没有\r,\n或者\r\n,那么我们知道此时读的文本肯定大于16kb,还有没有读完,此时调用fill方法读剩余的文本。然后重复上一步。下面是代码的详细注释。
public String readLine() throws IOException {
return readLine(false);
}
String readLine(boolean ignoreLF) throws IOException {
StringBuffer s = null; //主要作用是readline方法的值返回,注意的一点是如果读到缓存流最后没有读到换行符,则会执行 //s.append(cb, startChar, i - startChar);然后再去调用fill来重新填充缓存字符数组来读 //并直到读到换行符,来追加字符。
int startChar; //nextChar会赋值个startChar用于分割cb
synchronized (lock) { //加锁防止读入脏数据
ensureOpen(); //确保Reader in不是空的
boolean omitLF = ignoreLF || skipLF; //omitLF的值由skipLF的值的控制,ignoreLF在这里都是false,这个值 //主要用来处理换行符是\r\n的情况
bufferLoop: //循环的标签,并没啥用
for (;;) { //死循环,在循环中用return来打断死循环
if (nextChar >= nChars) //nchars表示cb缓存数组实际缓存文本的字节数大小。nextChar标识此时指向 //cb哪个字节数,如果nextChar = nChars = 0 标识此时还没有读文本, //如果nextChar和nChars不等于0,且满足nextChar >= nChar表示缓存读完。需重 //新填充缓存
fill(); //填充缓存
if (nextChar >= nChars) { /* EOF */
if (s != null && s.length() > 0)
return s.toString();
else
return null;
} //判断文件是否读完了。
boolean eol = false; //一行是否读完的标志
char c = 0;
int i;
/* Skip a leftover '\n', if necessary */
if (omitLF && (cb[nextChar] == '\n')) // 判断结尾/r/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;
}
} //判断是否有读到换行符,如果读到标识一行结束,设置eof为true
startChar = nextChar;
nextChar = i;
if (eol) {
String str;
if (s == null) {
str = new String(cb, startChar, i - startChar);
} else {
s.append(cb, startChar, i - startChar);
str = s.toString();
}
nextChar++;
if (c == '\r') {
skipLF = true;
}
return str;
} //如果读到了文件末尾,则直接截取cb作为字符串返回。
if (s == null)
s = new StringBuffer(defaultExpectedLineLength);
s.append(cb, startChar, i - startChar);
}
}
}
private void fill() throws IOException {
int dst;
if (markedChar <= UNMARKED) {
/* No mark */
dst = 0;
} else {
/* Marked */
int delta = nextChar - markedChar;
if (delta >= readAheadLimit) {
/* Gone past read-ahead limit: Invalidate mark */
markedChar = INVALIDATED;
readAheadLimit = 0;
dst = 0;
} else {
if (readAheadLimit <= cb.length) {
/* Shuffle in the current buffer */
System.arraycopy(cb, markedChar, cb, 0, delta);
markedChar = 0;
dst = delta;
} else {
/* Reallocate buffer to accommodate read-ahead limit */
char ncb[] = new char[readAheadLimit];
System.arraycopy(cb, markedChar, ncb, 0, delta);
cb = ncb;
markedChar = 0;
dst = delta;
}
nextChar = nChars = delta;
}
}
int n;
do {
n = in.read(cb, dst, cb.length - dst);
} while (n == 0);
if (n > 0) {
nChars = dst + n;
nextChar = dst;
}
}