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缓冲区数组中读取的字符所在的索引位置0nextCharnChars 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时skipLFtrue否则skipLFfalse 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;//设置右指针nChars0左指针nextChar0 } //构造函数需要传入一个被装饰的字符输入流缓冲区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;//dst0可以从缓冲区char[]数组的索引0的位置开始写入字符了 } else {//如果调用过mark()函数那么markedChar0 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个字符的话然后返回实际读入的字符总数量n0n8192 n in.read(cb, dst, cb.length - dst); } while (n 0); if (n 0) { //设置右指针nCharsdst n默认情况下就是设置nCharsn nChars dst n; //设置左指针nextChardst默认情况下就是设置nextChar0 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) { //如果换行符标志skipLFtrue的话说明在windows操作系统下前一个字符读到了\r此处设置换行符标志skipLFfalse 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,offlen)索引位置。 //该函数只被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[]数组的长度 //条件2markedChar-1 //条件3skipLFfalse 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) { //如果换行符标志skipLFtrue的话说明在windows操作系统下前一个字符读到了\r此处设置换行符标志skipLFfalse 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-nextChar2者取其小个字符到使用者指定的char[]数组cbuf的[off,offn)索引位置n就是len或者nChars-nextChar中2者取其小的值 System.arraycopy(cb, nextChar, cbuf, off, n); nextChar n;//左指针nextChar向前移动len或者nChars-nextChar2者取其小个索引位置 return n;//返回len或者nChars-nextChar2者取其小 } //函数内部做了线程同步从缓冲区char[]数组或者被装饰的字符输入流优先从缓冲区中读取len个字符到使用者指定的char[]数组cbuf中这len个字符被放到char[]数组cbuf的[off,offlen)索引位置。 public int read(char cbuf[], int off, int len) throws IOException { //用当前这个被装饰的字符输入流作为锁对象来同步线程 synchronized (lock) { ensureOpen();//检查被装饰的字符输入流是否关闭 //检查[off,offlen)索引是否在使用者指定的char[]数组cbuf中因为要读取len个字符到char[]数组cbuf的[off,offlen)索引位置所以如果[off,offlen)索引位置不在使用者指定的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、累计读取到了nnlen个字符但是缓冲区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所以只有当换行符标志skipLFtrue时omitLFtrue 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) { //读一个文件被装饰的字符输入流中非最后一行之前的其它所有行时eoltrue会走下面的逻辑 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(); } //将左指针nextChar1指向本次读取的一行字符中的第2个换行符\nwindows操作系统在缓冲区char[]数组中的索引位置以便下次在【标记点A】处直接跳过第2个换行符\n nextChar; if (c \r) { skipLF true;//设置换行符标志skipLFtrue以便下次再【标记点A】处直接跳过第2个换行符\n } return str;//返回从缓冲区char[]数组中读取的这一行非最后一行字符 } if (s null) //读一个文件被装饰的字符输入流中的最后一行时eolfalse会走下面的逻辑构造一个默认长度为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已经处理过了因此这里的换行符标志skipLFtrue则先跳过这个换行符\n这里的\n不计入将要跳过的字符总数量中但是接下来windows操作系统的换行符\r\n就要计入将要跳过的字符总数量中并重新设置换行符标志skipLFfalse 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) { //如果rd则将要跳过的n个字符都在缓冲区char[]数组中的[nextChar,nChars)索引位置移动左指针nextCharnextCharr即可跳过这n个字符完成本次skip()函数的调用 nextChar r; r 0;//置备份的数量r0用来最后结算本次实际从缓冲区char[]数组或者被装饰的字符输入流优先从缓冲区中累计跳过的字符数量 break;//并跳出循环 } else { //如果rd则缓冲区char[]数组中的[nextChar,nChars)索引位置的(nChars-nextChar)个字符不够将要跳过的n个字符则置备份的数量rr-d r - d; //然后移动左指针nextCharnChars以便下次循环的时候满足【场景二】可以从被装饰的字符输入流中继续向缓冲区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 StreamString lines() { IteratorString iter new IteratorString() { 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); } }
BufferedReader的源码分析和使用方法详细分析(windows操作系统,JDK8)
发布时间:2026/7/7 12:55:36
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缓冲区数组中读取的字符所在的索引位置0nextCharnChars 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时skipLFtrue否则skipLFfalse 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;//设置右指针nChars0左指针nextChar0 } //构造函数需要传入一个被装饰的字符输入流缓冲区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;//dst0可以从缓冲区char[]数组的索引0的位置开始写入字符了 } else {//如果调用过mark()函数那么markedChar0 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个字符的话然后返回实际读入的字符总数量n0n8192 n in.read(cb, dst, cb.length - dst); } while (n 0); if (n 0) { //设置右指针nCharsdst n默认情况下就是设置nCharsn nChars dst n; //设置左指针nextChardst默认情况下就是设置nextChar0 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) { //如果换行符标志skipLFtrue的话说明在windows操作系统下前一个字符读到了\r此处设置换行符标志skipLFfalse 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,offlen)索引位置。 //该函数只被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[]数组的长度 //条件2markedChar-1 //条件3skipLFfalse 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) { //如果换行符标志skipLFtrue的话说明在windows操作系统下前一个字符读到了\r此处设置换行符标志skipLFfalse 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-nextChar2者取其小个字符到使用者指定的char[]数组cbuf的[off,offn)索引位置n就是len或者nChars-nextChar中2者取其小的值 System.arraycopy(cb, nextChar, cbuf, off, n); nextChar n;//左指针nextChar向前移动len或者nChars-nextChar2者取其小个索引位置 return n;//返回len或者nChars-nextChar2者取其小 } //函数内部做了线程同步从缓冲区char[]数组或者被装饰的字符输入流优先从缓冲区中读取len个字符到使用者指定的char[]数组cbuf中这len个字符被放到char[]数组cbuf的[off,offlen)索引位置。 public int read(char cbuf[], int off, int len) throws IOException { //用当前这个被装饰的字符输入流作为锁对象来同步线程 synchronized (lock) { ensureOpen();//检查被装饰的字符输入流是否关闭 //检查[off,offlen)索引是否在使用者指定的char[]数组cbuf中因为要读取len个字符到char[]数组cbuf的[off,offlen)索引位置所以如果[off,offlen)索引位置不在使用者指定的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、累计读取到了nnlen个字符但是缓冲区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所以只有当换行符标志skipLFtrue时omitLFtrue 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) { //读一个文件被装饰的字符输入流中非最后一行之前的其它所有行时eoltrue会走下面的逻辑 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(); } //将左指针nextChar1指向本次读取的一行字符中的第2个换行符\nwindows操作系统在缓冲区char[]数组中的索引位置以便下次在【标记点A】处直接跳过第2个换行符\n nextChar; if (c \r) { skipLF true;//设置换行符标志skipLFtrue以便下次再【标记点A】处直接跳过第2个换行符\n } return str;//返回从缓冲区char[]数组中读取的这一行非最后一行字符 } if (s null) //读一个文件被装饰的字符输入流中的最后一行时eolfalse会走下面的逻辑构造一个默认长度为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已经处理过了因此这里的换行符标志skipLFtrue则先跳过这个换行符\n这里的\n不计入将要跳过的字符总数量中但是接下来windows操作系统的换行符\r\n就要计入将要跳过的字符总数量中并重新设置换行符标志skipLFfalse 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) { //如果rd则将要跳过的n个字符都在缓冲区char[]数组中的[nextChar,nChars)索引位置移动左指针nextCharnextCharr即可跳过这n个字符完成本次skip()函数的调用 nextChar r; r 0;//置备份的数量r0用来最后结算本次实际从缓冲区char[]数组或者被装饰的字符输入流优先从缓冲区中累计跳过的字符数量 break;//并跳出循环 } else { //如果rd则缓冲区char[]数组中的[nextChar,nChars)索引位置的(nChars-nextChar)个字符不够将要跳过的n个字符则置备份的数量rr-d r - d; //然后移动左指针nextCharnChars以便下次循环的时候满足【场景二】可以从被装饰的字符输入流中继续向缓冲区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 StreamString lines() { IteratorString iter new IteratorString() { 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); } }