如果使用者用的是默认的构造函数创建了BufferedReader的对象,并循环调用BufferedReader的readLine()函数来读取字符,如下所示:
package com.xxx.bio;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
public class BufferedReaderTest {
public static void main(String[] args) throws FileNotFoundException {
try (BufferedReader br = new BufferedReader(new FileReader("D:\\nio-data.txt"))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
我的windows操作系统的D盘根目录下有nio-data.txt文件,内容如下:

程序运行结果,如下所示:

以上代码的整个执行过程分为以下6步
①、先执行构造函数,构造一个长度为8192的char[] cb(缓冲区数组),右指针nChars=左指针nextChar=0,被装饰的字符输入流是FileReader.class对象,如下所示:
public class BufferedReader extends Reader {
...省略部分代码...
//构造函数,需要传入一个被装饰的字符输入流和缓冲区(char[] cb数组)的长度
public BufferedReader(Reader in, int sz) {
super(in);//设置Reader.class::lock变量指向的对象(锁)就是当前这个被装饰的字符输入流
if (sz <= 0)//校验,缓冲区(char[]字符数组)的长度必须>0
throw new IllegalArgumentException("Buffer size <= 0");
this.in = in;
cb = new char[sz];
nextChar = nChars = 0;//设置右指针nChars=0,左指针nextChar=0
}
//构造函数,需要传入一个被装饰的字符输入流,缓冲区(char cb[]数组)的长度是8192(默认值,16KB)
public BufferedReader(Reader in) {
this(in, defaultCharBufferSize);
}
...省略部分代码...
}

②、然后执行readLine()函数,流程如下所示:

③、在步骤②的readLine()函数执行过程中,从被装饰的字符输入流向缓冲区(char[]数组)的[nextChar,nChars)索引位置填充(nChars-nextChar)个字符的过程是通过fill()函数完成的(步骤②流程中的紫色部分),fill()函数的流程如下所示:

在这个流程中,从被装饰的字符输入流向缓冲区(char[] cb数组)中填充字符和修改nChars(右指针)、nextChar(左指针)的过程是通过上图中的红色部分,如下所示:
...省略部分代码...
int n;//从被装饰的字符输入流向缓冲区(char[]数组)中读入的字符数量
do {
//直接调用被装饰的字符输入流向缓冲区(char[]数组)的[dst,cb.length)索引位置读取cb.length - dst个字符
//默认情况下就是从被装饰的字符输入流向缓冲区(char[]数组)的[0,8192)索引位置读取8192个字符(如果被装饰的字符输入流有8192个字符的话),然后返回实际读入的字符总数量n,0<=n<=8192
n = in.read(cb, dst, cb.length - dst);
} while (n == 0);
if (n > 0) {
//设置右指针nChars=dst + n,默认情况下就是设置nChars=n
nChars = dst + n;
//设置左指针nextChar=dst,默认情况下就是设置nextChar=0
nextChar = dst;
}
...省略部分代码...

此时,被装饰的字符输入流(FileReader)中的字符和缓冲区(char[]数组)中的字符,如下所示:

④、当步骤③中的fill()函数执行完之后,就会继续执行步骤②中readLine()函数流程中的黄色部分,如下所示:

在结束charLoop标记位置的循环之后,i=7,startChar=0,nextChar=7,eol=true,c='\r',skipLF = false,然后继续执行步骤②中readLine()函数流程中的黄色部分,如下所示:

在黄色流程中的紫色节点读取到了第1行的字符串String str="只要热爱生命。",如下所示:

返回字符串str="只要热爱生命。",此时,全局变量(成员变量)nextChar(左指针)=8,skipLF(换行符标志) = true,如下所示:

⑤、当使用者在自己的while循环中第2次执行readLine()函数时,流程如下所示(只执行紫色部分,不会再执行fill()函数):


在结束charLoop标记位置的循环之后,i=19,startChar=9,nextChar=19,eol=false,c='。',skipLF = false,然后继续执行下面流程中的紫色部分,如下所示:

在紫色部分中的黄色节点读取到了第2行的字符串String str="一切,都在意料之中。",如下所示:

此时,全局变量(成员变量)nextChar(左指针)=19,skipLF(换行符标志) = false,再次进入bufferLoop循环点的循环时,会执行fill()函数,但是由于被装饰的字符输入流中的字符已经使用完了,即使执行fill()函数,也无法向缓冲区(char[] cb数组)填充任何字符了,导致nextChar(左指针)== nChars(右指针)==19 ,然后返回字符串str="一切,都在意料之中。",如下所示:

⑥、当使用者在自己的while循环中第3次执行readLine()函数时,流程如下所示(只执行紫色部分,会执行fill()函数,但是不会改变nextChar(左指针)和 nChars(右指针),此时nextChar(左指针)== nChars(右指针)==19),并且返回null:

四、没有执行mark()函数的前提下BufferedReader的read(char cbuf[])函数的执行过程
public class BufferedReader extends Reader {
...省略部分代码...
//被装饰的字符输入流
private Reader in;
//缓冲区数组,默认长度为8192
private char cb[];
//nChars,右指针,表示从被装饰的字符输入流中顺序读取到char cb[](缓冲区数组)中的所有有效字符中的最后一个字符在char[] cb(缓冲区数组)的索引位置
//nextChar,左指针,表示当前将要从char[] cb(缓冲区数组)中读取的字符所在的索引位置,0<=nextChar<=nChars
private int nChars, nextChar;
//-2表示markedChar为无效标记时的状态
private static final int INVALIDATED = -2;
//-1表示markedChar为未标记时的状态
private static final int UNMARKED = -1;
//在char cb[](缓冲区数组)中标记1个索引位置,在mark()函数中,设置markedChar = nextChar,-1和-2都表示没有在char[] cb(缓冲区数组)中做过标记。
private int markedChar = UNMARKED;
// 只有当markedChar > 0时,才有用,具体作用待补充
private int readAheadLimit = 0;
//换行符标志,在windows操作系统下(换行符是'\r''\n'这2个字符的组合),当左指针nextChar指向的char[] cb(缓冲区数组)中的字符为'\n'时,skipLF=true,否则skipLF=false
private boolean skipLF = false;
//当使用mark()和reset()功能时,才有用,在mark()函数中,设置markedSkipLF = skipLF
private boolean markedSkipLF = false;
// 默认缓冲区(char[]字符数组)大小为8192 字节(16KB)
private static int defaultCharBufferSize = 8192;
//StringBuffer默认的长度,当使用readLine()函数时,先给StringBuffer设置默认长度为80,然后再从char[] cb(缓冲区数组)中读取字符到StringBuffer中来构成字符串
private static int defaultExpectedLineLength = 80;
//从缓冲区(char[]数组)或被装饰的字符输入流(优先从缓冲区)中读取len个字符到使用者指定的char[]数组cbuf中,这len个字符被放到char[]数组cbuf的[off,off+len)索引位置。
//该函数只被read()函数调用
private int read1(char[] cbuf, int off, int len) throws IOException {
// 左指针nextChar >= 右指针nChars有以下2种情况
//场景一:缓冲区(char[]数组)为空(还没有从被装饰的字符输入流中填充任何字符进去),nextChar = nChars =0
//场景二:从被装饰的字符输入流向缓冲区(char[]数组)缓存的字符(如果被装饰的字符输入流有8192个字符的话,默认情况下是8192个字符)已经被读完了,nextChar = nChars = 缓存的有效字符中的最后一个字符在char[] cb(缓冲区数组)的索引位置
if (nextChar >= nChars) {
if (len >= cb.length && markedChar <= UNMARKED && !skipLF) {
//如果同时满足以下3个条件的话,则直接从被装饰的字符输入流中读取len个字符到使用者指定的char[]数组cbuf中,并返回从被装饰的字符输入流中实际读到的字符数量:
//条件1:读取到使用者指定的char[]数组cbuf中的len个字符>=缓冲区(char[]数组)的长度
//条件2:markedChar<=-1
//条件3:skipLF=false
return in.read(cbuf, off, len);
}
fill();//不满足以上3个条件的话,执行fill()函数
}
//如果执行此时左指针nextChar >= 右指针nChars的话,说明缓冲区(char[]数组)和被装饰的字符输入流中都已经没有可以读取的字符了,此时返回-1
if (nextChar >= nChars) return -1;
//有换行符的情况下,不会把换行符读取到使用者指定的char[]数组cbuf中,在windows操作系统下(换行符是'\r''\n'这2个字符的组合)
if (skipLF) {
//如果换行符标志skipLF=true的话,说明在windows操作系统下,前一个字符读到了'\r',此处设置换行符标志skipLF=false
skipLF = false;
if (cb[nextChar] == '\n') {
//在windows操作系统下(换行符是'\r''\n'这2个字符的组合),如果前一个字符读到了'\r',这里肯定会读到'\n',那么直接跳过'\n'这个字符,继续读取下一行的字符
nextChar++;
if (nextChar >= nChars)
//跳过换行符之后如果左指针nextChar >= 右指针nChars(左右指针相遇),执行fill()函数填充缓冲区(char[]数组)
fill();
if (nextChar >= nChars)
//如果执行完fill()函数并填充完缓冲区(char[]数组)之后,左指针nextChar >= 右指针nChars(左右指针仍然在一起),说明缓冲区(char[]数组)和被装饰的字符输入流中都已经没有可以读取的字符了,此时返回-1
return -1;
}
}
//nChars - nextChar表示缓冲区(char[]数组)中可以被读取的字符数量
int n = Math.min(len, nChars - nextChar);
//从缓冲区(char[]数组)的左指针nextChar索引开始,读取len或者(nChars-nextChar)(2者取其小)个字符到使用者指定的char[]数组cbuf的[off,off+n)索引位置(n就是len或者(nChars-nextChar)中2者取其小的值)
System.arraycopy(cb, nextChar, cbuf, off, n);
nextChar += n;//左指针nextChar向前移动len或者(nChars-nextChar)(2者取其小)个索引位置
return n;//返回len或者(nChars-nextChar)(2者取其小)
}
//函数内部做了线程同步,从缓冲区(char[]数组)或者被装饰的字符输入流(优先从缓冲区)中读取len个字符到使用者指定的char[]数组cbuf中,这len个字符被放到char[]数组cbuf的[off,off+len)索引位置。
public int read(char cbuf[], int off, int len) throws IOException {
//用当前这个被装饰的字符输入流作为锁对象来同步线程
synchronized (lock) {
ensureOpen();//检查被装饰的字符输入流是否关闭
//检查[off,off+len)索引是否在使用者指定的char[]数组cbuf中,因为要读取len个字符到char[]数组cbuf的[off,off+len)索引位置,所以,如果[off,off+len)索引位置不在使用者指定的char[]数组cbuf中的话,抛出一个IndexOutOfBoundsException异常
if ((off < 0) || (off > cbuf.length) || (len < 0) ||
((off + len) > cbuf.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
//如果要从缓冲区(char[]数组)中读取的len个字符==0时,返回0
return 0;
}
//先从缓冲区(char[]数组)或者被装饰的字符输入流中读取len个字符到使用者指定的char[]数组cbuf中,如果从这2个地方读取不到任何字符到使用者指定的char[]数组cbuf中的话,返回-1,如果从这2个地方可以读取到字符的话,返回实际读取到的字符数量
int n = read1(cbuf, off, len);
//如果第一次就无法从缓冲区(char[]数组)中读取任何字符的话,则返回-1,在没有执行mark()函数和reset()函数的前提下,有以下2种情况:
//场景一:左指针nextChar = 右指针nChars = 0,并且被装饰的字符输入流中已经没有任何可以读取的字符了
//场景二:左指针nextChar = 右指针nChars = 有效字符中的最后一个字符在char[] cb(缓冲区数组)的索引位置,并且被装饰的字符输入流中已经没有任何可以读取的字符了
if (n <= 0) return n;
//如果第一次从缓冲区(char[]数组)或者被装饰的字符输入流中读取了n个字符到使用者指定的char[]数组cbuf中之后,并且n(第一次读取的字符数量) < len(要读取的目标字符数量)的话
//继续从缓冲区(char[]数组)或者被装饰的字符输入流中读取字符,退出循环的条件有以下2个:
//条件a、累计读取到了len(要读取的目标字符数量)个字符;
//条件b、累计读取到了n(n<len)个字符,但是缓冲区(char[]数组)或者被装饰的字符输入流中已经没有可以被读取的字符了。
while ((n < len) && in.ready()) {//从这里退出循环的话,符合条件a
//每次从缓冲区(char[]数组)或者被装饰的字符输入流中读取的字符数量
int n1 = read1(cbuf, off + n, len - n);
if (n1 <= 0) break;//从这里退出循环的话符合条件b
n += n1;
}
return n;//返回累计从缓冲区(char[]数组)或者被装饰的字符输入流中读取到使用者指定的char[]数组cbuf中的字符总数量
}
}
...省略部分代码...
}
如果使用者用的是默认的构造函数创建了BufferedReader的对象,并循环调用BufferedReader的 read(char cbuf[])函数来读取字符,如下所示:
package com.xxx.bio;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
public class BufferedReaderTest {
public static void main(String[] args) throws FileNotFoundException {
try (BufferedReader br = new BufferedReader(new FileReader("D:\\nio-data.txt"))) {
char[] buffer = new char[1024];
int length;
while ((length = br.read(buffer)) != -1) {
System.out.print(new String(buffer, 0, length));
}
} catch (IOException e) {
e.printStackTrace();
}
}
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)