15、BufferedReader的源码分析和使用方法详细分析(windows操作系统,JDK8)

一、BufferedReader的源码------带有缓冲区的字符流装饰器类

  在计算机中,文本是最普遍、最基础的数据交换格式,从日志文件到 JSON API,从数据库查询结果到机器学习训练集,文本无处不在。然而,原始的字符输入流(如 FileReader 或 InputStreamReader)每次读取操作都可能触发一次底层的系统调用,这对于需要逐行或逐字符处理的场景来说,性能开销是灾难性的。BufferedReader是自 Java 1.1 起就存在的用于文本处理的装饰器类,其内置的缓冲区char cb\[\]数组(默认长度是8192,占16k内存)可以保证使用者在调用readLine() 函数、read()函数时可以减少底层 IO 的操作次数,而不用让JVM在进行IO时每次都切换到操作系统的内核态进行一次昂贵的磁盘或网络 I/O。因此,也可以把BufferedReader叫做带有缓冲区的字符流装饰器类。

  BufferedReader.class 的UML关系图,如下所示:

  BufferedReader.class 的源码,如下所示:

复制代码
package java.io;

import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

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[] 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);
    }

    //检查被装饰的字符输入流是否关闭
    private void ensureOpen() throws IOException {
        if (in == null)
            throw new IOException("Stream closed");
    }
    
    //从被装饰的字符输入流向缓冲区(char[]数组)的[nextChar,nChars)索引位置填充(nChars-nextChar)个字符
    private void fill() throws IOException {
        int dst;
        if (markedChar <= UNMARKED) {//如果还没有调用过mark()函数,那么markedChar=-1
            dst = 0;//dst=0,可以从缓冲区(char[]数组)的索引0的位置开始写入字符了
        } else {//如果调用过mark()函数,那么markedChar>=0
            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;//从被装饰的字符输入流向缓冲区(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;
        }
    }

    //函数内部做了线程同步,从缓冲区(char[]数组)中读取1个字符
    public int read() throws IOException {
        synchronized (lock) {//用当前这个被装饰的字符输入流作为锁对象来同步线程
            ensureOpen();//检查被装饰的字符输入流是否关闭
            for (;;) {
                // 左指针nextChar >= 右指针nChars有以下2种情况
                //场景一:缓冲区(char[]数组)为空(还没有从被装饰的字符输入流中填充任何字符进去),nextChar = nChars =0
                //场景二:从被装饰的字符输入流向缓冲区(char[]数组)缓存的字符(如果被装饰的字符输入流有8192个字符的话,默认情况下是8192个字符)已经被读完了,nextChar = nChars = 缓存的有效字符中的最后一个字符在char[] cb(缓冲区数组)的索引位置
                if (nextChar >= nChars) {
                    //场景一的情况下执行fill()函数是第一次从被装饰的输入流中读取字符填充缓冲区(char[]数组)
                    //场景二的情况下执行fill()函数是再次从被装饰的输入流中读取字符重新填充缓冲区(char[]数组)
                    fill();
                    if (nextChar >= nChars)
                        //如果执行完fill()函数后,nextChar = nChars =0或者nextChar = nChars = 有效字符中的最后一个字符在char[] cb(缓冲区数组)的索引位置
                        //那么就说明被装饰的字符输入流和缓冲区(char[]数组)中的有效字符都已经被读取完了,返回-1
                        return -1;
                }
                if (skipLF) {
                    //如果换行符标志skipLF=true的话,说明在windows操作系统下,前一个字符读到了'\r',此处设置换行符标志skipLF=false
                    skipLF = false;
                    if (cb[nextChar] == '\n') {
                        //在windows操作系统下(换行符是'\r''\n'这2个字符的组合),如果前一个字符读到了'\r',这里肯定会读到'\n',那么直接跳过'\n'这个字符,继续读取下一行的字符
                        nextChar++;
                        continue;
                    }
                }
                return cb[nextChar++];//windows操作系统下,非换行符'\r''\n'的情况下,正常读取左指针nextChar指向的字符
            }
        }
    }
    
    //从缓冲区(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中的字符总数量
        }
    }

    //函数内部做了线程同步,从缓冲区(char[]数组)或者被装饰的字符输入流(优先从缓冲区)中读取一行字符,在windows操作系统下(换行符是'\r''\n'这2个字符的组合)
    String readLine(boolean ignoreLF) throws IOException {
        //本次调用所读取的一行字符
        StringBuffer s = null;
        //本次将要读取的一行字符在缓冲区(char[]数组)中的起始索引位置
        int startChar;

        synchronized (lock) {//用当前这个被装饰的字符输入流作为锁对象来同步线程
            ensureOpen();//检查被装饰的字符输入流是否关闭
            //在windows操作系统上使用BufferedReader.class::readLine()函数时,ignoreLF永远=false,所以只有当换行符标志skipLF=true时,omitLF=true
            boolean omitLF = ignoreLF || skipLF;

        bufferLoop:
            for (;;) {
                // 左指针nextChar >= 右指针nChars有以下2种情况
                //场景一:缓冲区(char[]数组)为空(还没有从被装饰的字符输入流中填充任何字符进去),nextChar = nChars =0
                //场景二:从被装饰的字符输入流向缓冲区(char[]数组)缓存的字符(如果被装饰的字符输入流有8192个字符的话,默认情况下是8192个字符)已经被读完了,nextChar = nChars = 缓存的有效字符中的最后一个字符在char[] cb(缓冲区数组)的索引位置
                if (nextChar >= nChars)
                    //场景一的情况下执行fill()函数是第一次从被装饰的输入流中读取字符填充缓冲区(char[]数组)
                    //场景二的情况下执行fill()函数是再次从被装饰的输入流中读取字符重新填充缓冲区(char[]数组)
                    fill();                
                if (nextChar >= nChars) { /* EOF */
                    //标记点B
                    //填充完缓冲区(char[]数组)之后,如果左指针nextChar >= 右指针nChars,则说明缓冲区(char[]数组)和被装饰的字符输入流中都已经没有可以读取的字符了,此时本次调用所读取的这一行StringBuffer对象有2中可能性
                    //①、StringBuffer s != null 并且StringBuffer s中缓存了部分字符,将这个StringBuffer s对象通过toString()函数转换为字符串并返回
                    //②、StringBuffer s == null 或者StringBuffer s中没有缓存任何字符,直接返回null
                    if (s != null && s.length() > 0)
                        return s.toString();//满足条件①
                    else
                        return null;//满足条件②
                }
                //读取一行字符时,这一行字符将要被读取结束的标志位(换行符)
                //true表示已经读取到了这一行的结束标志位(换行符),在windows操作系统下的换行符'\r''\n',肯定是读到'\r'了
                //false表示还没有读取到了这一行的结束标志位(换行符)
                boolean eol = false;
                char c = 0;//在一行字符的读取过程中,当前读到的字符
                int i;//windows操作系统下用来找到换行符'\r'在缓冲区(char[]数组)中的索引位置

                /* Skip a leftover '\n', if necessary */
                if (omitLF && (cb[nextChar] == '\n'))//标记点A
                    nextChar++;//windows操作系统下读到'\r'之后就要跳过'\n'(因为'\r''\n'合起来在windows操作系统中组成了换行符)
                skipLF = false;
                omitLF = false;

            charLoop:
                //在windows操作系统下开始找换行符'\r''\n'中的第1个字符'\r'在缓冲区(char[]数组)中的索引位置,这个索引位置用变量i表示,退出这个循环的条件有2个
                //条件a、找到第1个字符'\r'在缓冲区(char[]数组)中的索引位置之后;
                //条件b、当i(或者说左指针nextChar)==右指针nChars时。
                for (i = nextChar; i < nChars; i++) {
                    c = cb[i];
                    if ((c == '\n') || (c == '\r')) {
                        //找到第1个字符'\r'在缓冲区(char[]数组)中的索引位置之后,设置boolean eol = true,并结束循环
                        eol = true;
                        break charLoop;
                    }
                }
                
                startChar = nextChar;//完整的一行字符在缓冲区(char[]数组)中的起始索引位置
                nextChar = i;//完整的一行字符的换行符'\r'在缓冲区(char[]数组)中的结束索引位置

                if (eol) {
                    //读一个文件(被装饰的字符输入流)中非最后一行之前的其它所有行时,eol=true,会走下面的逻辑
                    String str;
                    if (s == null) {
                        //从缓冲区(char[]数组)中取出[startChar, i - startChar)索引位置的字符(一个文件(被装饰的字符输入流)中非最后一行之前的其它所有行)组成字符串
                        str = new String(cb, startChar, i - startChar);
                    } else {
                        s.append(cb, startChar, i - startChar);
                        str = s.toString();
                    }
                    //将左指针nextChar+1,指向本次读取的一行字符中的第2个换行符'\n'(windows操作系统)在缓冲区(char[]数组)中的索引位置,以便下次在【标记点A】处直接跳过第2个换行符'\n'
                    nextChar++;
                    if (c == '\r') {
                        skipLF = true;//设置换行符标志skipLF=true,以便下次再【标记点A】处直接跳过第2个换行符'\n'
                    }
                    return str;//返回从缓冲区(char[]数组)中读取的这一行(非最后一行)字符
                }

                if (s == null)
                    //读一个文件(被装饰的字符输入流)中的最后一行时,eol=false,会走下面的逻辑,构造一个默认长度为80的StringBuffer去拼接最后一行的所有字符
                    s = new StringBuffer(defaultExpectedLineLength);
                //从缓冲区(char[]数组)中取出[startChar, i - startChar)索引位置的字符(一个文件(被装饰的字符输入流)中非最后一行)组成字符串,并且在上面的【标记点B】处的【条件①】中返回
                s.append(cb, startChar, i - startChar);
            }
        }
    }

    //windows操作系统下用BufferedReader对象从被装饰的字符输入流中读取一行字符的时候,一般只会用readLine()函数,而不会直接用readLine(boolean ignoreLF)函数
    public String readLine() throws IOException {
        return readLine(false);
    }

    //函数内部做了线程同步,从缓冲区(char[]数组)或者被装饰的字符输入流(优先从缓冲区)中跳过n个字符,并返回实际从缓冲区(char[]数组)或者被装饰的字符输入流(优先从缓冲区)中跳过的字符数量
    public long skip(long n) throws IOException {
        if (n < 0L) {
            throw new IllegalArgumentException("skip value is negative");
        }
        synchronized (lock) {//用当前这个被装饰的字符输入流作为锁对象来同步线程
            ensureOpen();//检查被装饰的字符输入流是否关闭
            //先将累计要从缓冲区(char[]数组)或者被装饰的字符输入流(优先从缓冲区)中跳过的字符数量进行一个备份,用来最后结算本次实际从缓冲区(char[]数组)或者被装饰的字符输入流(优先从缓冲区)中累计跳过的字符数量
            long r = n;
            while (r > 0) {//当备份的要从缓冲区(char[]数组)或者被装饰的字符输入流(优先从缓冲区)中跳过的字符数量>0时,才进入循环
                if (nextChar >= nChars)
                    // 左指针nextChar >= 右指针nChars有以下2种情况
                    //场景一:缓冲区(char[]数组)为空(还没有从被装饰的字符输入流中填充任何字符进去),nextChar = nChars =0;
                    //场景二:已经从缓冲区(char[]数组)中跳过了(nChars-nextChar)个字符,但是(nChars-nextChar)<n(即已经跳过的字符数量不够n个),则接着从缓冲区(char[]数组)中跳过字符,但是需要从被装饰的字符输入流中继续向缓冲区(char[]数组)中填充字符。
                    fill();
                if (nextChar >= nChars) /* EOF */
                    //如果此时左指针nextChar >= 右指针nChars,说明缓冲区(char[]数组)或者被装饰的字符输入流中的所有字符都已经被跳过了,没有多余的字符可以继续跳过了(即文件和被装饰的字符输入流已经EOF了),那么,退出循环
                    break;
                if (skipLF) {
                    //windows操作系统下,如果这个时候刚好遇到了换行符'\n'('\r'已经处理过了,因此这里的换行符标志skipLF=true),则先跳过这个换行符'\n'(这里的'\n'不计入将要跳过的字符总数量中,但是接下来windows操作系统的换行符'\r''\n'就要计入将要跳过的字符总数量中),并重新设置换行符标志skipLF=false
                    skipLF = false;
                    if (cb[nextChar] == '\n') {
                        nextChar++;//跳过这个换行符'\n'(这里的'\n'不计入将要跳过的字符总数量中,但是接下来windows操作系统的换行符'\r''\n'就要计入将要跳过的字符总数量中)
                    }
                }
                //本次从缓冲区(char[]数组)中要跳过的(nChars-nextChar)个字符,这些被跳过的字符在缓冲区(char[]数组)的[nextChar,nChars)索引位置
                long d = nChars - nextChar;
                if (r <= d) {
                    //如果r<=d,则将要跳过的n个字符都在缓冲区(char[]数组)中的[nextChar,nChars)索引位置,移动左指针nextChar=nextChar+r即可跳过这n个字符,完成本次skip()函数的调用
                    nextChar += r;
                    r = 0;//置备份的数量r=0,用来最后结算本次实际从缓冲区(char[]数组)或者被装饰的字符输入流(优先从缓冲区)中累计跳过的字符数量
                    break;//并跳出循环
                }
                else {
                    //如果r>d,则缓冲区(char[]数组)中的[nextChar,nChars)索引位置的(nChars-nextChar)个字符不够将要跳过的n个字符,则置备份的数量r=r-d;
                    r -= d;
                    //然后移动左指针nextChar=nChars,以便下次循环的时候满足【场景二】,可以从被装饰的字符输入流中继续向缓冲区(char[]数组)中填充字符,来继续跳过之前剩余还未跳过的(n-d)个字符
                    nextChar = nChars;
                }
            }
            return n - r;//结算本次实际从缓冲区(char[]数组)或者被装饰的字符输入流(优先从缓冲区)中累计实际跳过的字符数量,并返回
        }
    }

    public boolean ready() throws IOException {
        synchronized (lock) {
            ensureOpen();

            /*
             * If newline needs to be skipped and the next char to be read
             * is a newline character, then just skip it right away.
             */
            if (skipLF) {
                /* Note that in.ready() will return true if and only if the next
                 * read on the stream will not block.
                 */
                if (nextChar >= nChars && in.ready()) {
                    fill();
                }
                if (nextChar < nChars) {
                    if (cb[nextChar] == '\n')
                        nextChar++;
                    skipLF = false;
                }
            }
            return (nextChar < nChars) || in.ready();
        }
    }

    public boolean markSupported() {
        return true;
    }

    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;
        }
    }

    public void close() throws IOException {
        synchronized (lock) {
            if (in == null)
                return;
            try {
                in.close();
            } finally {
                in = null;
                cb = null;
            }
        }
    }

    //JDK8以后新增了流式编程的支持
    public Stream<String> lines() {
        Iterator<String> iter = new Iterator<String>() {
            String nextLine = null;

            @Override
            public boolean hasNext() {
                if (nextLine != null) {
                    return true;
                } else {
                    try {
                        nextLine = readLine();
                        return (nextLine != null);
                    } catch (IOException e) {
                        throw new UncheckedIOException(e);
                    }
                }
            }

            @Override
            public String next() {
                if (nextLine != null || hasNext()) {
                    String line = nextLine;
                    nextLine = null;
                    return line;
                } else {
                    throw new NoSuchElementException();
                }
            }
        };
        return StreamSupport.stream(Spliterators.spliteratorUnknownSize(
                iter, Spliterator.ORDERED | Spliterator.NONNULL), false);
    }
}

二、没有执行mark()函数的前提下BufferedReader的read()函数的执行过程

复制代码
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[]数组)中读取1个字符
    public int read() throws IOException {
        synchronized (lock) {//用当前这个被装饰的字符输入流作为锁对象来同步线程
            ensureOpen();//检查被装饰的字符输入流是否关闭
            for (;;) {
                // 左指针nextChar >= 右指针nChars有以下2种情况
                //场景一:缓冲区(char[]数组)为空(还没有从被装饰的字符输入流中填充任何字符进去),nextChar = nChars =0
                //场景二:从被装饰的字符输入流向缓冲区(char[]数组)缓存的字符(如果被装饰的字符输入流有8192个字符的话,默认情况下是8192个字符)已经被读完了,nextChar = nChars = 缓存的有效字符中的最后一个字符在char[] cb(缓冲区数组)的索引位置
                if (nextChar >= nChars) {
                    //场景一的情况下执行fill()函数是第一次从被装饰的输入流中读取字符填充缓冲区(char[]数组)
                    //场景二的情况下执行fill()函数是再次从被装饰的输入流中读取字符重新填充缓冲区(char[]数组)
                    fill();
                    if (nextChar >= nChars)
                        //如果执行完fill()函数后,nextChar = nChars =0或者nextChar = nChars = 有效字符中的最后一个字符在char[] cb(缓冲区数组)的索引位置
                        //那么就说明被装饰的字符输入流和缓冲区(char[]数组)中的有效字符都已经被读取完了,返回-1
                        return -1;
                }
                if (skipLF) {
                    //如果换行符标志skipLF=true的话,说明在windows操作系统下,前一个字符读到了'\r',此处设置换行符标志skipLF=false
                    skipLF = false;
                    if (cb[nextChar] == '\n') {
                        //在windows操作系统下(换行符是'\r''\n'这2个字符的组合),如果前一个字符读到了'\r',这里肯定会读到'\n',那么直接跳过'\n'这个字符,继续读取下一行的字符
                        nextChar++;
                        continue;
                    }
                }
                return cb[nextChar++];//windows操作系统下,非换行符'\r''\n'的情况下,正常读取左指针nextChar指向的字符
            }
        }
    }        
    
    //从被装饰的字符输入流向缓冲区(char[]数组)的[nextChar,nChars)索引位置填充(nChars-nextChar)个字符
    private void fill() throws IOException {
        int dst;
        if (markedChar <= UNMARKED) {//如果还没有调用过mark()函数,那么markedChar=-1
            dst = 0;//dst=0,可以从缓冲区(char[]数组)的索引0的位置开始写入字符了
        } else {//如果调用过mark()函数,那么markedChar>=0
            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;//从被装饰的字符输入流向缓冲区(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;
        }
    }
    ...省略部分代码...
}

如果使用者用的是默认的构造函数创建了BufferedReader的对象,并循环调用BufferedReader的read()函数来读取字符,如下所示:

复制代码
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"))) {
            int character;
            while ((character = br.read()) != -1) {
                System.out.print((char) character);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

我的windows操作系统的D盘根目录下有nio-data.txt文件,内容如下:

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

整个执行过程分为以下5步

①、先执行构造函数,构造一个长度为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);
    }
    ...省略部分代码...
}

②、然后执行read()函数中线程同步的代码片段,流程如下所示:

③、在步骤②的read()函数执行过程中,从被装饰的字符输入流向缓冲区(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()函数执行完之后,就会继续执行步骤②中read()函数流程中的黄色部分,返回缓冲区(char\[\]数组)中nextChar(左指针)索引位置的字符,然后更新nextChar(左指针)=nextChar+1,如下所示:

⑤、接下来,每次调用read()函数时都只会执行步骤②流程中的红色部分,并且每次只返回缓冲区(char\[\]数组)中nextChar(左指针)指向的索引位置的字符,如下所示:

直到第20次调用read()函数时,nextChar(左指针)= nChars(右指针)=19,就会执行步骤②流程中的黄色部分,如下所示:

最终退出使用者代码中的while循环,如下所示:

复制代码
            while ((character = br.read()) != -1) {
                System.out.print((char) character);
            }

三、没有执行mark()函数的前提下BufferedReader的readLine()函数的执行过程

复制代码
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[]数组)或者被装饰的字符输入流(优先从缓冲区)中读取一行字符,在windows操作系统下(换行符是'\r''\n'这2个字符的组合)
    String readLine(boolean ignoreLF) throws IOException {
        //本次调用所读取的一行字符
        StringBuffer s = null;
        //本次将要读取的一行字符在缓冲区(char[]数组)中的起始索引位置
        int startChar;

        synchronized (lock) {//用当前这个被装饰的字符输入流作为锁对象来同步线程
            ensureOpen();//检查被装饰的字符输入流是否关闭
            //在windows操作系统上使用BufferedReader.class::readLine()函数时,ignoreLF永远=false,所以只有当换行符标志skipLF=true时,omitLF=true
            boolean omitLF = ignoreLF || skipLF;

        bufferLoop:
            for (;;) {
                // 左指针nextChar >= 右指针nChars有以下2种情况
                //场景一:缓冲区(char[]数组)为空(还没有从被装饰的字符输入流中填充任何字符进去),nextChar = nChars =0
                //场景二:从被装饰的字符输入流向缓冲区(char[]数组)缓存的字符(如果被装饰的字符输入流有8192个字符的话,默认情况下是8192个字符)已经被读完了,nextChar = nChars = 缓存的有效字符中的最后一个字符在char[] cb(缓冲区数组)的索引位置
                if (nextChar >= nChars)
                    //场景一的情况下执行fill()函数是第一次从被装饰的输入流中读取字符填充缓冲区(char[]数组)
                    //场景二的情况下执行fill()函数是再次从被装饰的输入流中读取字符重新填充缓冲区(char[]数组)
                    fill();                
                if (nextChar >= nChars) { /* EOF */
                    //标记点B
                    //填充完缓冲区(char[]数组)之后,如果左指针nextChar >= 右指针nChars,则说明缓冲区(char[]数组)和被装饰的字符输入流中都已经没有可以读取的字符了,此时本次调用所读取的这一行StringBuffer对象有2中可能性
                    //①、StringBuffer s != null 并且StringBuffer s中缓存了部分字符,将这个StringBuffer s对象通过toString()函数转换为字符串并返回
                    //②、StringBuffer s == null 或者StringBuffer s中没有缓存任何字符,直接返回null
                    if (s != null && s.length() > 0)
                        return s.toString();//满足条件①
                    else
                        return null;//满足条件②
                }
                //读取一行字符时,这一行字符将要被读取结束的标志位(换行符)
                //true表示已经读取到了这一行的结束标志位(换行符),在windows操作系统下的换行符'\r''\n',肯定是读到'\r'了
                //false表示还没有读取到了这一行的结束标志位(换行符)
                boolean eol = false;
                char c = 0;//在一行字符的读取过程中,当前读到的字符
                int i;//windows操作系统下用来找到换行符'\r'在缓冲区(char[]数组)中的索引位置

                /* Skip a leftover '\n', if necessary */
                if (omitLF && (cb[nextChar] == '\n'))//标记点A
                    nextChar++;//windows操作系统下读到'\r'之后就要跳过'\n'(因为'\r''\n'合起来在windows操作系统中组成了换行符)
                skipLF = false;
                omitLF = false;

            charLoop:
                //在windows操作系统下开始找换行符'\r''\n'中的第1个字符'\r'在缓冲区(char[]数组)中的索引位置,这个索引位置用变量i表示,退出这个循环的条件有2个
                //条件a、找到第1个字符'\r'在缓冲区(char[]数组)中的索引位置之后;
                //条件b、当i(或者说左指针nextChar)==右指针nChars时。
                for (i = nextChar; i < nChars; i++) {
                    c = cb[i];
                    if ((c == '\n') || (c == '\r')) {
                        //找到第1个字符'\r'在缓冲区(char[]数组)中的索引位置之后,设置boolean eol = true,并结束循环
                        eol = true;
                        break charLoop;
                    }
                }
                
                startChar = nextChar;//完整的一行字符在缓冲区(char[]数组)中的起始索引位置
                nextChar = i;//完整的一行字符的换行符'\r'在缓冲区(char[]数组)中的结束索引位置

                if (eol) {
                    //读一个文件(被装饰的字符输入流)中非最后一行之前的其它所有行时,eol=true,会走下面的逻辑
                    String str;
                    if (s == null) {
                        //从缓冲区(char[]数组)中取出[startChar, i - startChar)索引位置的字符(一个文件(被装饰的字符输入流)中非最后一行之前的其它所有行)组成字符串
                        str = new String(cb, startChar, i - startChar);
                    } else {
                        s.append(cb, startChar, i - startChar);
                        str = s.toString();
                    }
                    //将左指针nextChar+1,指向本次读取的一行字符中的第2个换行符'\n'(windows操作系统)在缓冲区(char[]数组)中的索引位置,以便下次在【标记点A】处直接跳过第2个换行符'\n'
                    nextChar++;
                    if (c == '\r') {
                        skipLF = true;//设置换行符标志skipLF=true,以便下次再【标记点A】处直接跳过第2个换行符'\n'
                    }
                    return str;//返回从缓冲区(char[]数组)中读取的这一行(非最后一行)字符
                }

                if (s == null)
                    //读一个文件(被装饰的字符输入流)中的最后一行时,eol=false,会走下面的逻辑,构造一个默认长度为80的StringBuffer去拼接最后一行的所有字符
                    s = new StringBuffer(defaultExpectedLineLength);
                //从缓冲区(char[]数组)中取出[startChar, i - startChar)索引位置的字符(一个文件(被装饰的字符输入流)中非最后一行)组成字符串,并且在上面的【标记点B】处的【条件①】中返回
                s.append(cb, startChar, i - startChar);
            }
        }
    }

    //windows操作系统下用BufferedReader对象从被装饰的字符输入流中读取一行字符的时候,一般只会用readLine()函数,而不会直接用readLine(boolean ignoreLF)函数
    public String readLine() throws IOException {
        return readLine(false);
    }
    
    //从被装饰的字符输入流向缓冲区(char[]数组)的[nextChar,nChars)索引位置填充(nChars-nextChar)个字符
    private void fill() throws IOException {
        int dst;
        if (markedChar <= UNMARKED) {//如果还没有调用过mark()函数,那么markedChar=-1
            dst = 0;//dst=0,可以从缓冲区(char[]数组)的索引0的位置开始写入字符了
        } else {//如果调用过mark()函数,那么markedChar>=0
            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;//从被装饰的字符输入流向缓冲区(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;
        }
    }
    ...省略部分代码...
}

如果使用者用的是默认的构造函数创建了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();
        }
    }
}

我的windows操作系统的D盘根目录下有nio-data.txt文件,内容如下:

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

以上代码的整个执行过程总共执行了使用者代码中的2次while循环,分为以下7步(第1次while循环在①⑤步骤中,第2次while循环在⑥⑦步骤中)

①、先执行构造函数,构造一个长度为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);
    }
    ...省略部分代码...
}

②、在第1次进入使用者代码的while循环后,先执行父类Reader.class::read(char cbuf\[\])函数,父类Reader.class的read(char cbuf\[\])函数会调用子类

BufferedReader.class实现的read(char cbuf\[\], int off, int len)函数,如下所示:

复制代码
public abstract class Reader implements Readable, Closeable {
    ...省略部分代码...
    public int read(char cbuf[]) throws IOException {
        return read(cbuf, 0, cbuf.length);
    }
    
    abstract public int read(char cbuf[], int off, int len) throws IOException;
    ...省略部分代码...
}

BufferedReader.class::read(char cbuf\[\], int off, int len)函数的执行过程,如下所示:

③、在步骤②的BufferedReader.class::read(char cbuf\[\], int off, int len)函数执行过程中,向使用者指定的char\[\]数组cbuf的off,off+len)索引位置上读取字符的过程是通过read1(char\[ cbuf, int off, int len)函数来完成的(步骤②流程中的紫色部分),此时nextChar(左指针) = nChars(右指针) = 0,使用者指定的char\[\]数组cbuf为空,skipLF(换行符标志)= false,缓冲区(char cb\[\]数组)的长度是8192(默认值),int markedChar = -1,read1()(read1()函数没有重载,也可以这样来表示)函数的流程如下所示:

④、在步骤③的read1()函数执行过程中,从被装饰的字符输入流向缓冲区(char\[\]数组)的nextChar,nChars)索引位置填充(nChars-nextChar)个字符的过程是通过fill()函数完成的(步骤③流程中的紫色部分),此时nextChar(左指针) = nChars(右指针) = 0,使用者指定的char\[数组cbuf为空,skipLF(换行符标志)= false,缓冲区(char cb\[\]数组)的长度是8192(默认值),int markedChar = -1,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()函数之后,此时nextChar(左指针)=0 , nChars(右指针) = 19,使用者指定的char\[\]数组cbuf为空,skipLF(换行符标志)= false,缓冲区(char cb\[\]数组)的长度是8192(默认值),int markedChar = -1

⑤、然后继续执行步骤③中的流程(只执行紫色部分)

此时会将缓冲区(char\[\] cb数组)中0,19)索引位置的字符通过System.arraycopy()函数全部复制到使用者指定的char\[数组cbuf的[0,19)索引位置,并移动nextChar(左指针)= 0+19 = 19,最后返回19给read1()函数的调用者read()函数,如下所示:

然后继续执行步骤②中的流程(只执行紫色部分)

返回给使用者19。

⑥、然后第2次进入使用者代码的while循环,还是先执行父类Reader.class::read(char cbuf\[\])函数,父类Reader.class的read(char cbuf\[\])函数会调用子类BufferedReader.class实现的read(char cbuf\[\], int off, int len)函数,BufferedReader.class::read(char cbuf\[\], int off, int len)函数的执行过程,如下所示(只执行紫色部分):

此时,在read()函数中调用read1(cbuf, off, len)函数时,read1(cbuf, off, len)函数的返回值是-1,所以直接退出线程同步的代码片段(退出操作系统Monitor监控的代码片段),然后返回给使用者-1。其中read1(cbuf, off, len)函数的执行过程,如下所示(只执行紫色部分):

在这次read1(cbuf, off, len)函数的调用中,也会调用了fill()函数,如下所示(只执行紫色部分):

与步骤④不同的是,本次fill()函数执行完以后,nextChar(左指针)和nChars(右指针)没有变化,nextChar(左指针)== nChars(右指针) == 19,使用者指定的char\[\]数组cbuf与步骤⑤中的内容相同,skipLF(换行符标志)= false,缓冲区(char cb\[\]数组)的长度是8192(默认值),int markedChar = -1,如下所示:

⑦、因为步骤⑥给使用者代码返回了-1,所以使用者代码中的第2次while循环结束之后,就会退出使用者代码中的while循环,到此,使用者代码中的2次while循环全部结束。