Java 中的 BufferedReader 介绍_动力节点Java学院整理
BufferedReader介绍
BufferedReader是缓冲字符输入流。它继承于Reader。
BufferedReader的作用是为其他字符输入流添加一些缓冲功能。
BufferedReader函数列表
BufferedReader(Readerin) BufferedReader(Readerin,intsize) voidclose() voidmark(intmarkLimit) booleanmarkSupported() intread() intread(char[]buffer,intoffset,intlength) StringreadLine() booleanready() voidreset() longskip(longcharCount)
BufferedReader源码分析(基于jdk1.7.40)
packagejava.io; publicclassBufferedReaderextendsReader{ privateReaderin; //字符缓冲区 privatecharcb[]; //nChars是cb缓冲区中字符的总的个数 //nextChar是下一个要读取的字符在cb缓冲区中的位置 privateintnChars,nextChar; //表示“标记无效”。它与UNMARKED的区别是: //(01)UNMARKED是压根就没有设置过标记。 //(02)而INVALIDATED是设置了标记,但是被标记位置太长,导致标记无效! privatestaticfinalintINVALIDATED=-2; //表示没有设置“标记” privatestaticfinalintUNMARKED=-1; //“标记” privateintmarkedChar=UNMARKED; //“标记”能标记位置的最大长度 privateintreadAheadLimit=0;/*ValidonlywhenmarkedChar>0*/ //skipLF(即skipLineFeed)是“是否忽略换行符”标记 privatebooleanskipLF=false; //设置“标记”时,保存的skipLF的值 privatebooleanmarkedSkipLF=false; //默认字符缓冲区大小 privatestaticintdefaultCharBufferSize=8192; //默认每一行的字符个数 privatestaticintdefaultExpectedLineLength=80; //创建“Reader”对应的BufferedReader对象,sz是BufferedReader的缓冲区大小 publicBufferedReader(Readerin,intsz){ super(in); if(sz<=0) thrownewIllegalArgumentException("Buffersize<=0"); this.in=in; cb=newchar[sz]; nextChar=nChars=0; } //创建“Reader”对应的BufferedReader对象,默认的BufferedReader缓冲区大小是8k publicBufferedReader(Readerin){ this(in,defaultCharBufferSize); } //确保“BufferedReader”是打开状态 privatevoidensureOpen()throwsIOException{ if(in==null) thrownewIOException("Streamclosed"); } //填充缓冲区函数。有以下两种情况被调用: //(01)缓冲区没有数据时,通过fill()可以向缓冲区填充数据。 //(02)缓冲区数据被读完,需更新时,通过fill()可以更新缓冲区的数据。 privatevoidfill()throwsIOException{ //dst表示“cb中填充数据的起始位置”。 intdst; if(markedChar<=UNMARKED){ //没有标记的情况,则设dst=0。 dst=0; }else{ //delta表示“当前标记的长度”,它等于“下一个被读取字符的位置”减去“标记的位置”的差值; intdelta=nextChar-markedChar; if(delta>=readAheadLimit){ //若“当前标记的长度”超过了“标记上限(readAheadLimit)”, //则丢弃标记! markedChar=INVALIDATED; readAheadLimit=0; dst=0; }else{ if(readAheadLimit<=cb.length){ //若“当前标记的长度”没有超过了“标记上限(readAheadLimit)”, //并且“标记上限(readAheadLimit)”小于/等于“缓冲的长度”; //则先将“下一个要被读取的位置,距离我们标记的置符的距离”间的字符保存到cb中。 System.arraycopy(cb,markedChar,cb,0,delta); markedChar=0; dst=delta; }else{ //若“当前标记的长度”没有超过了“标记上限(readAheadLimit)”, //并且“标记上限(readAheadLimit)”大于“缓冲的长度”; //则重新设置缓冲区大小,并将“下一个要被读取的位置,距离我们标记的置符的距离”间的字符保存到cb中。 charncb[]=newchar[readAheadLimit]; System.arraycopy(cb,markedChar,ncb,0,delta); cb=ncb; markedChar=0; dst=delta; } //更新nextChar和nChars nextChar=nChars=delta; } } intn; do{ //从“in”中读取数据,并存储到字符数组cb中; //从cb的dst位置开始存储,读取的字符个数是cb.length-dst //n是实际读取的字符个数;若n==0(即一个也没读到),则继续读取! n=in.read(cb,dst,cb.length-dst); }while(n==0); //如果从“in”中读到了数据,则设置nChars(cb中字符的数目)=dst+n, //并且nextChar(下一个被读取的字符的位置)=dst。 if(n>0){ nChars=dst+n; nextChar=dst; } } //从BufferedReader中读取一个字符,该字符以int的方式返回 publicintread()throwsIOException{ synchronized(lock){ ensureOpen(); for(;;){ //若“缓冲区的数据已经被读完”, //则先通过fill()更新缓冲区数据 if(nextChar>=nChars){ fill(); if(nextChar>=nChars) return-1; } //若要“忽略换行符”, //则对下一个字符是否是换行符进行处理。 if(skipLF){ skipLF=false; if(cb[nextChar]=='\n'){ nextChar++; continue; } } //返回下一个字符 returncb[nextChar++]; } } } //将缓冲区中的数据写入到数组cbuf中。off是数组cbuf中的写入起始位置,len是写入长度 privateintread(char[]cbuf,intoff,intlen)throwsIOException{ //若“缓冲区的数据已经被读完”,则更新缓冲区数据。 if(nextChar>=nChars){ if(len>=cb.length&&markedChar<=UNMARKED&&!skipLF){ returnin.read(cbuf,off,len); } fill(); } //若更新数据之后,没有任何变化;则退出。 if(nextChar>=nChars)return-; //若要“忽略换行符”,则进行相应处理 if(skipLF){ skipLF=false; if(cb[nextChar]=='\n'){ nextChar++; if(nextChar>=nChars) fill(); if(nextChar>=nChars) return-1; } } //拷贝字符操作 intn=Math.min(len,nChars-nextChar); System.arraycopy(cb,nextChar,cbuf,off,n); nextChar+=n; returnn; } //对read()的封装,添加了“同步处理”和“阻塞式读取”等功能 publicintread(charcbuf[],intoff,intlen)throwsIOException{ synchronized(lock){ ensureOpen(); if((off<0)||(off>cbuf.length)||(len<0)|| ((off+len)>cbuf.length)||((off+len)<0)){ thrownewIndexOutOfBoundsException(); }elseif(len==0){ return0; } intn=read1(cbuf,off,len); if(n<=0)returnn; while((n=nChars) fill(); if(nextChar>=nChars){/*EOF*/ if(s!=null&&s.length()>0) returns.toString(); else returnnull; } booleaneol=false; charc=0; inti; /*Skipaleftover'\n',ifnecessary*/ if(omitLF&&(cb[nextChar]=='\n')) nextChar++; skipLF=false; omitLF=false; charLoop: for(i=nextChar;i 0){ if(nextChar>=nChars) fill(); if(nextChar>=nChars)/*EOF*/ break; if(skipLF){ skipLF=false; if(cb[nextChar]=='\n'){ nextChar++; } } longd=nChars-nextChar; if(r<=d){ nextChar+=r; r=0; break; } else{ r-=d; nextChar=nChars; } } returnn-r; } } //“下一个字符”是否可读 publicbooleanready()throwsIOException{ synchronized(lock){ ensureOpen(); //若忽略换行符为true; //则判断下一个符号是否是换行符,若是的话,则忽略 if(skipLF){ if(nextChar>=nChars&&in.ready()){ fill(); } if(nextChar 说明:
要想读懂BufferReader的源码,就要先理解它的思想。BufferReader的作用是为其它Reader提供缓冲功能。创建BufferReader时,我们会通过它的构造函数指定某个Reader为参数。BufferReader会将该Reader中的数据分批读取,每次读取一部分到缓冲中;操作完缓冲中的这部分数据之后,再从Reader中读取下一部分的数据。
为什么需要缓冲呢?原因很简单,效率问题!缓冲中的数据实际上是保存在内存中,而原始数据可能是保存在硬盘或NandFlash中;而我们知道,从内存中读取数据的速度比从硬盘读取数据的速度至少快10倍以上。
那干嘛不干脆一次性将全部数据都读取到缓冲中呢?第一,读取全部的数据所需要的时间可能会很长。第二,内存价格很贵,容量不想硬盘那么大。
下面,我就BufferReader中最重要的函数fill()进行说明。其它的函数很容易理解,我就不详细介绍了,大家可以参考源码中的注释进行理解。我们先看看fill()的源码:
privatevoidfill()throwsIOException{ intdst; if(markedChar<=UNMARKED){ /*Nomark*/ dst=0; }else{ /*Marked*/ intdelta=nextChar-markedChar; if(delta>=readAheadLimit){ /*Gonepastread-aheadlimit:Invalidatemark*/ markedChar=INVALIDATED; readAheadLimit=0; dst=0; }else{ if(readAheadLimit<=cb.length){ /*Shuffleinthecurrentbuffer*/ System.arraycopy(cb,markedChar,cb,0,delta); markedChar=0; dst=delta; }else{ /*Reallocatebuffertoaccommodateread-aheadlimit*/ charncb[]=newchar[readAheadLimit]; System.arraycopy(cb,markedChar,ncb,0,delta); cb=ncb; markedChar=0; dst=delta; } nextChar=nChars=delta; } } intn; do{ n=in.read(cb,dst,cb.length-dst); }while(n==0); if(n>0){ nChars=dst+n; nextChar=dst; } }根据fill()中的if...else...,我将fill()分为4种情况进行说明。
情况1:读取完缓冲区的数据,并且缓冲区没有被标记
执行流程如下,
(01)其它函数调用fill(),来更新缓冲区的数据
(02)fill()执行代码if(markedChar<=UNMARKED){...}
为了方便分析,我们将这种情况下fill()执行的操作等价于以下代码:
privatevoidfill()throwsIOException{ intdst; if(markedChar<=UNMARKED){ /*Nomark*/ dst=0; } intn; do{ n=in.read(cb,dst,cb.length-dst); }while(n==0); if(n>0){ nChars=dst+n; nextChar=dst; } }说明:
这种情况发生的情况是——Reader中有很长的数据,我们每次从中读取一部分数据到缓冲中进行操作。每次当我们读取完缓冲中的数据之后,并且此时BufferedReader没有被标记;那么,就接着从Reader(BufferReader提供缓冲功能的Reader)中读取下一部分的数据到缓冲中。
其中,判断是否读完缓冲区中的数据,是通过“比较nextChar和nChars之间大小”来判断的。其中,nChars是缓冲区中字符的总的个数,而nextChar是缓冲区中下一个要读取的字符的位置。
判断BufferedReader有没有被标记,是通过“markedChar”来判断的。
理解这个思想之后,我们再对这种情况下的fill()的代码进行分析,就特别容易理解了。
(01)if(markedChar<=UNMARKED)它的作用是判断“BufferedReader是否被标记”。若被标记,则dst=0。
(02)in.read(cb,dst,cb.length-dst)等价于in.read(cb,0,cb.length),意思是从Reader对象in中读取cb.length个数据,并存储到缓冲区cb中,而且从缓冲区cb的位置0开始存储。该函数返回值等于n,也就是n表示实际读取的字符个数。若n=0(即没有读取到数据),则继续读取,直到读到数据为止。
(03)nChars=dst+n等价于nChars=n;意味着,更新缓冲区数据cb之后,设置nChars(缓冲区的数据个数)为n。
(04)nextChar=dst等价于nextChar=0;意味着,更新缓冲区数据cb之后,设置nextChar(缓冲区中下一个会被读取的字符的索引值)为0。
情况2:读取完缓冲区的数据,缓冲区的标记位置>0,并且“当前标记的长度”超过“标记上限(readAheadLimit)”
执行流程如下,
(01)其它函数调用fill(),来更新缓冲区的数据
(02)fill()执行代码if(delta>=readAheadLimit){...}
为了方便分析,我们将这种情况下fill()执行的操作等价于以下代码:
privatevoidfill()throwsIOException{ intdst; if(markedChar>UNMARKED){ intdelta=nextChar-markedChar; if(delta>=readAheadLimit){ markedChar=INVALIDATED; readAheadLimit=0; dst=0; } } intn; do{ n=in.read(cb,dst,cb.length-dst); }while(n==0); if(n>0){ nChars=dst+n; nextChar=dst; } }说明:
这种情况发生的情况是——BufferedReader中有很长的数据,我们每次从中读取一部分数据到缓冲区中进行操作。当我们读取完缓冲区中的数据之后,并且此时,BufferedReader存在标记时,同时,“当前标记的长度”大于“标记上限”;那么,就发生情况2。此时,我们会丢弃“标记”并更新缓冲区。
(01)delta=nextChar-markedChar;其中,delta就是“当前标记的长度”,它是“下一个被读取字符的位置”减去“被标记的位置”的差值。
(02)if(delta>=readAheadLimit);其中,当delta>=readAheadLimit,就意味着,“当前标记的长度”>=“标记上限”。为什么要有标记上限,即readAheadLimit的值到底有何意义呢?
我们标记一个位置之后,更新缓冲区的时候,被标记的位置会被保存;当我们不停的更新缓冲区的时候,被标记的位置会被不停的放大。然后内存的容量是有效的,我们不可能不限制长度的存储标记。所以,需要readAheadLimit来限制标记长度!
(03)in.read(cb,dst,cb.length-dst)等价于in.read(cb,0,cb.length),意思是从Reader对象in中读取cb.length个数据,并存储到缓冲区cb中,而且从缓冲区cb的位置0开始存储。该函数返回值等于n,也就是n表示实际读取的字符个数。若n=0(即没有读取到数据),则继续读取,直到读到数据为止。
(04)nChars=dst+n等价于nChars=n;意味着,更新缓冲区数据cb之后,设置nChars(缓冲区的数据个数)为n。
(05)nextChar=dst等价于nextChar=0;意味着,更新缓冲区数据cb之后,设置nextChar(缓冲区中下一个会被读取的字符的索引值)为0。
情况3:读取完缓冲区的数据,缓冲区的标记位置>0,“当前标记的长度”没超过“标记上限(readAheadLimit)”,并且“标记上限(readAheadLimit)”小于/等于“缓冲的长度”;
执行流程如下,(01)其它函数调用fill(),来更新缓冲区的数据
(02)fill()执行代码if(readAheadLimit<=cb.length){...}
为了方便分析,我们将这种情况下fill()执行的操作等价于以下代码:
privatevoidfill()throwsIOException{ intdst; if(markedChar>UNMARKED){ intdelta=nextChar-markedChar; if((delta0){ nChars=dst+n; nextChar=dst; } } 说明:
这种情况发生的情况是——BufferedReader中有很长的数据,我们每次从中读取一部分数据到缓冲区中进行操作。当我们读取完缓冲区中的数据之后,并且此时,BufferedReader存在标记时,同时,“当前标记的长度”小于“标记上限”,并且“标记上限”小于/等于“缓冲区长度”;那么,就发生情况3。此时,我们保留“被标记的位置”(即,保留被标记位置开始的数据),并更新缓冲区(将新增的数据,追加到保留的数据之后)。
情况4:读取完缓冲区的数据,缓冲区的标记位置>0,“当前标记的长度”没超过“标记上限(readAheadLimit)”,并且“标记上限(readAheadLimit)”大于“缓冲的长度”;
执行流程如下,
(01)其它函数调用fill(),来更新缓冲区的数据
(02)fill()执行代码else{charncb[]=newchar[readAheadLimit];...}
为了方便分析,我们将这种情况下fill()执行的操作等价于以下代码:
privatevoidfill()throwsIOException{ intdst; if(markedChar>UNMARKED){ intdelta=nextChar-markedChar; if((deltacb.length)){ charncb[]=newchar[readAheadLimit]; System.arraycopy(cb,markedChar,ncb,0,delta); cb=ncb; markedChar=0; dst=delta; nextChar=nChars=delta; } } intn; do{ n=in.read(cb,dst,cb.length-dst); }while(n==); if(n>){ nChars=dst+n; nextChar=dst; } } 说明:
这种情况发生的情况是——BufferedReader中有很长的数据,我们每次从中读取一部分数据到缓冲区中进行操作。当我们读取完缓冲区中的数据之后,并且此时,BufferedReader存在标记时,同时,“当前标记的长度”小于“标记上限”,并且“标记上限”大于“缓冲区长度”;那么,就发生情况4。此时,我们要先更新缓冲区的大小,然后再保留“被标记的位置”(即,保留被标记位置开始的数据),并更新缓冲区数据(将新增的数据,追加到保留的数据之后)。
示例代码
关于BufferedReader中API的详细用法,参考示例代码(BufferedReaderTest.java):
importjava.io.BufferedReader; importjava.io.ByteArrayInputStream; importjava.io.File; importjava.io.InputStream; importjava.io.FileReader; importjava.io.IOException; importjava.io.FileNotFoundException; importjava.lang.SecurityException; /** *BufferedReader测试程序 * * */ publicclassBufferedReaderTest{ privatestaticfinalintLEN=5; publicstaticvoidmain(String[]args){ testBufferedReader(); } /** *BufferedReader的API测试函数 */ privatestaticvoidtestBufferedReader(){ //创建BufferedReader字符流,内容是ArrayLetters数组 try{ Filefile=newFile("bufferedreader.txt"); BufferedReaderin= newBufferedReader( newFileReader(file)); //从字符流中读取5个字符。“abcde” for(inti=0;i程序中读取的bufferedreader.txt的内容如下:
abcdefghijklmnopqrstuvwxyz 0123456789 ABCDEFGHIJKLMNOPQRSTUVWXYZ运行结果:
0:a 1:b 2:c 3:d 4:e buf=01234 readLine=56789 buf=fghij以上所述是小编给大家介绍的Java中的BufferedReader介绍_动力节点Java学院整理,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对毛票票网站的支持!