Java-io之BufferedReader

BufferReader的作用是为其它Reader提供缓冲功能。创建BufferReader时,我们会通过它的构造函数指定某个Reader为参数。BufferReader会将该Reader中的数据分批读取,每次读取一部分到缓冲中;操作完缓冲中的这部分数据之后,再从Reader中读取下一部分的数据。

一般而言, 每个Reader的read()都会对其绑定的字节/字符流发出相应的读操作,所以建议用BufferedReader封装Reader,因为read()操作可能是高代价的,比如FileReader。

1
BufferedReader in = new BufferedReader(new FileReader("foo.in"));

BufferedReader相比于BufferedInputStream,主要增加可对’\n’与’\r’的处理,其他方法都类似,接下来主要会对新增的readLine()详细解析一下。

BufferedReader的字段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//BufferedReader绑定的输入流
private Reader in;
//缓冲区
private char cb[];
//缓冲区中字符总数 下一个读取的字符位置
private int nChars, nextChar;
//无效mark
private static final int INVALIDATED = -2;
//没有mark
private static final int UNMARKED = -1;
//mark位置
private int markedChar = UNMARKED;
//mark失效的最大长度(只在markedChar > 0时有效)
private int readAheadLimit = 0;
//是否跳过'\n'
private boolean skipLF = false;
//mark()方法执行时记录skipLF的状态
private boolean markedSkipLF = false;
//默认缓冲区大小
private static int defaultCharBufferSize = 8192;
默认一行的字符数
private static int defaultExpectedLineLength = 80;

填充缓冲区方法fill()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
private void fill() throws IOException {
//填充的起始位置
int dst;
//若执行了mark()则markedChar>=0, markedChar<=UNMARKED代表没有执行mark()
if (markedChar <= UNMARKED) {
dst = 0;
} else {
//delta是需要保留的字符数
int delta = nextChar - markedChar;
//如果需要保存的字符数 > readAheadLimit(预读上限)则mark失效
if (delta >= readAheadLimit) {
markedChar = INVALIDATED;
readAheadLimit = 0;
dst = 0;
} else {
//预读上限 < 缓冲区长度
//就直接把cb[markedChar~ markedChar+delta]复制到cb[0~delta]
if (readAheadLimit <= cb.length) {
/* Shuffle in the current buffer */
System.arraycopy(cb, markedChar, cb, 0, delta);
markedChar = 0;
dst = delta;
} else {
//预读上限 > 缓冲区长度 此时缓冲区可以进行扩容
char ncb[] = new char[readAheadLimit];
System.arraycopy(cb, markedChar, ncb, 0, delta);
cb = ncb;
markedChar = 0;
dst = delta;
}
nextChar = nChars = delta;
}
}

//调用read()填满缓冲区
//阻塞读 读到至少一个字符
int n;
do {
n = in.read(cb, dst, cb.length - dst);
} while (n == 0);
if (n > 0) {
nChars = dst + n;
nextChar = dst;
}
}

读取方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//read()读取一个字符
public int read() throws IOException {
synchronized (lock) {
ensureOpen();
for (;;) {
//缓冲区已读完 调用fill()
if (nextChar >= nChars) {
fill();
//EOF 输入流数据读取完毕
if (nextChar >= nChars)
return -1;
}
//如果skipLF==true则跳过'\n'
if (skipLF) {
//TODO 为何要将skipLF置false呢?
skipLF = false;
if (cb[nextChar] == '\n') {
nextChar++;
continue;
}
}
return cb[nextChar++];
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
//从流中读取len个字符存入cbuf从off开始的位置
//是public read()的core
private int read1(char[] cbuf, int off, int len) throws IOException {
//流中数据读取完毕
if (nextChar >= nChars) {
//当需要读取的字符数>缓冲区长度并且也没有进行mark操作也不需要跳过'\n'时
//没有必要将需要读取的字符填充到缓冲区cb中再拷贝到cbuf中
//直接将数据读入到cbuf即可
//因为不需要用到BufferedReader提供的功能(mark/skipLF) 所以直接使用Reader.read()即可
if (len >= cb.length && markedChar <= UNMARKED && !skipLF) {
return in.read(cbuf, off, len);
}
fill();
}
//EOF
if (nextChar >= nChars) return -1;
//跳过'\n'
if (skipLF) {
skipLF = false;
if (cb[nextChar] == '\n') {
nextChar++;
if (nextChar >= nChars)
fill();
if (nextChar >= nChars)
return -1;
}
}
//看看填充进来的字符够不够len个
int n = Math.min(len, nChars - nextChar);
//有多少放多少进cbuf
System.arraycopy(cb, nextChar, cbuf, off, n);
nextChar += n;
//返回存放进cbuf的字符数
return n;
}

//增加对形参的数据校验
public int read(char cbuf[], int off, int len) throws IOException {
synchronized (lock) {
ensureOpen();
if ((off < 0) || (off > cbuf.length) || (len < 0) ||
((off + len) > cbuf.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}

int n = read1(cbuf, off, len);
//EOF
if (n <= 0) return n;
//若read1(cbuf, off, len)读取到缓冲区末尾 此时n依然小于len
//则判断输入流in是否可读 若可则继续read1()(进行新一轮的填充与读取)
while ((n < len) && in.ready()) {
int n1 = read1(cbuf, off + n, len - n);
//EOF
if (n1 <= 0) break;
n += n1;
}
return n;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
//读取一行字符 关于line的定义:
//一行被认为是由换行符('\ n'),回车符('\ r')或后面紧跟换行符的回车符中的任何一个终止。
String readLine(boolean ignoreLF) throws IOException {
//存储一行字符结果
StringBuffer s = null;
int startChar;

synchronized (lock) {
ensureOpen();
//是否忽略'\n' 只要形参ignoreLF与字段skipLF其一忽略则忽略
boolean omitLF = ignoreLF || skipLF;

//循环读取字符
bufferLoop:
for (;;) {
//缓冲区读取完毕 填充
if (nextChar >= nChars)
fill();
//EOF 输出结果s
if (nextChar >= nChars) {
if (s != null && s.length() > 0)
return s.toString();
else
return null;
}

//eol指示是否有'\n'与'\r'
boolean eol = false;
char c = 0;
int i;

//忽略'\n'
if (omitLF && (cb[nextChar] == '\n'))
nextChar++;
skipLF = false;
omitLF = false;

//循环查找第一个'\r'或者'\n'的位置
//i记录位置 eol标记有没有
charLoop:
for (i = nextChar; i < nChars; i++) {
c = cb[i];
if ((c == '\n') || (c == '\r')) {
eol = true;
break charLoop;
}
}

startChar = nextChar;
nextChar = i;

//如有换行字符'\n'与'\r' 就保存cb[startChar]到cb[i-1]的字符
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;
}

//若没有换行字符 则保存全部缓冲区字符
if (s == null)
s = new StringBuffer(defaultExpectedLineLength);
s.append(cb, startChar, i - startChar);
}
}
}

跳过多个字节skip

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
跳过n个字符
public long skip(long n) throws IOException {
if (n < 0L) {
throw new IllegalArgumentException("skip value is negative");
}
synchronized (lock) {
ensureOpen();
//r表示还需要跳过的字符数
long r = n;

//阻塞式skip
while (r > 0) {
if (nextChar >= nChars)
fill();
if (nextChar >= nChars) /* EOF */
break;
if (skipLF) {
skipLF = false;
if (cb[nextChar] == '\n') {
nextChar++;
}
}
long d = nChars - nextChar;
if (r <= d) {
nextChar += r;
r = 0;
break;
}
else {
r -= d;
nextChar = nChars;
}
}
//返回跳过的字符数
return n - r;
}
}
Donate here.