Java 中的 BufferedReader 介绍_动力节点Java学院整理
bufferedreader 介绍
bufferedreader 是缓冲字符输入流。它继承于reader。
bufferedreader 的作用是为其他字符输入流添加一些缓冲功能。
bufferedreader 函数列表
bufferedreader(reader in) bufferedreader(reader in, int size) void close() void mark(int marklimit) boolean marksupported() int read() int read(char[] buffer, int offset, int length) string readline() boolean ready() void reset() long skip(long charcount)
bufferedreader 源码分析(基于jdk1.7.40)
package java.io; public class bufferedreader extends reader { private reader in; // 字符缓冲区 private char cb[]; // nchars 是cb缓冲区中字符的总的个数 // nextchar 是下一个要读取的字符在cb缓冲区中的位置 private int nchars, nextchar; // 表示“标记无效”。它与unmarked的区别是: // (01) unmarked 是压根就没有设置过标记。 // (02) 而invalidated是设置了标记,但是被标记位置太长,导致标记无效! private static final int invalidated = -2; // 表示没有设置“标记” private static final int unmarked = -1; // “标记” private int markedchar = unmarked; // “标记”能标记位置的最大长度 private int readaheadlimit = 0; /* valid only when markedchar > 0 */ // skiplf(即skip line feed)是“是否忽略换行符”标记 private boolean skiplf = false; // 设置“标记”时,保存的skiplf的值 private boolean markedskiplf = false; // 默认字符缓冲区大小 private static int defaultcharbuffersize = 8192; // 默认每一行的字符个数 private static int defaultexpectedlinelength = 80; // 创建“reader”对应的bufferedreader对象,sz是bufferedreader的缓冲区大小 public bufferedreader(reader in, int sz) { super(in); if (sz <= 0) throw new illegalargumentexception("buffer size <= 0"); this.in = in; cb = new char[sz]; nextchar = nchars = 0; } // 创建“reader”对应的bufferedreader对象,默认的bufferedreader缓冲区大小是8k public bufferedreader(reader in) { this(in, defaultcharbuffersize); } // 确保“bufferedreader”是打开状态 private void ensureopen() throws ioexception { if (in == null) throw new ioexception("stream closed"); } // 填充缓冲区函数。有以下两种情况被调用: // (01) 缓冲区没有数据时,通过fill()可以向缓冲区填充数据。 // (02) 缓冲区数据被读完,需更新时,通过fill()可以更新缓冲区的数据。 private void fill() throws ioexception { // dst表示“cb中填充数据的起始位置”。 int dst; if (markedchar <= unmarked) { // 没有标记的情况,则设dst=0。 dst = 0; } else { // delta表示“当前标记的长度”,它等于“下一个被读取字符的位置”减去“标记的位置”的差值; int delta = 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中。 char ncb[] = new char[readaheadlimit]; system.arraycopy(cb, markedchar, ncb, 0, delta); cb = ncb; markedchar = 0; dst = delta; } // 更新nextchar和nchars nextchar = nchars = delta; } } int n; 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的方式返回 public int read() throws ioexception { synchronized (lock) { ensureopen(); for (;;) { // 若“缓冲区的数据已经被读完”, // 则先通过fill()更新缓冲区数据 if (nextchar >= nchars) { fill(); if (nextchar >= nchars) return -1; } // 若要“忽略换行符”, // 则对下一个字符是否是换行符进行处理。 if (skiplf) { skiplf = false; if (cb[nextchar] == '\n') { nextchar++; continue; } } // 返回下一个字符 return cb[nextchar++]; } } } // 将缓冲区中的数据写入到数组cbuf中。off是数组cbuf中的写入起始位置,len是写入长度 private int read(char[] cbuf, int off, int len) throws ioexception { // 若“缓冲区的数据已经被读完”,则更新缓冲区数据。 if (nextchar >= nchars) { if (len >= cb.length && markedchar <= unmarked && !skiplf) { return in.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; } } // 拷贝字符操作 int n = math.min(len, nchars - nextchar); system.arraycopy(cb, nextchar, cbuf, off, n); nextchar += n; return n; } // 对read()的封装,添加了“同步处理”和“阻塞式读取”等功能 public int read(char cbuf[], int off, int len) throws ioexception { synchronized (lock) { ensureopen(); if ((off < 0) || (off > cbuf.length) || (len < 0) || ((off + len) > cbuf.length) || ((off + len) < 0)) { throw new indexoutofboundsexception(); } else if (len == 0) { return 0; } int n = read1(cbuf, off, len); if (n <= 0) return n; while ((n < len) && in.ready()) { int n1 = read1(cbuf, off + n, len - n); if (n1 <= 0) break; n += n1; } return n; } } // 读取一行数据。ignorelf是“是否忽略换行符” string readline(boolean ignorelf) throws ioexception { stringbuffer s = null; int startchar; synchronized (lock) { ensureopen(); boolean omitlf = ignorelf || skiplf; bufferloop: for (;;) { if (nextchar >= nchars) fill(); if (nextchar >= nchars) { /* eof */ if (s != null && s.length() > 0) return s.tostring(); else return null; } boolean eol = false; char c = 0; int i; /* skip a leftover '\n', if necessary */ if (omitlf && (cb[nextchar] == '\n')) nextchar++; skiplf = false; omitlf = false; charloop: for (i = nextchar; i < nchars; i++) { c = cb[i]; if ((c == '\n') || (c == '\r')) { eol = true; break charloop; } } startchar = nextchar; nextchar = i; if (eol) { string str; if (s == null) { str = new string(cb, startchar, i - startchar); } else { s.append(cb, startchar, i - startchar); str = s.tostring(); } nextchar++; if (c == '\r') { skiplf = true; } return str; } if (s == null) s = new stringbuffer(defaultexpectedlinelength); s.append(cb, startchar, i - startchar); } } } // 读取一行数据。不忽略换行符 public string readline() throws ioexception { return readline(false); } // 跳过n个字符 public long skip(long n) throws ioexception { if (n < 0l) { throw new illegalargumentexception("skip value is negative"); } synchronized (lock) { ensureopen(); long r = n; while (r > 0) { if (nextchar >= nchars) fill(); if (nextchar >= nchars) /* eof */ break; if (skiplf) { skiplf = false; if (cb[nextchar] == '\n') { nextchar++; } } long d = nchars - nextchar; if (r <= d) { nextchar += r; r = 0; break; } else { r -= d; nextchar = nchars; } } return n - r; } } // “下一个字符”是否可读 public boolean ready() throws ioexception { synchronized (lock) { ensureopen(); // 若忽略换行符为true; // 则判断下一个符号是否是换行符,若是的话,则忽略 if (skiplf) { if (nextchar >= nchars && in.ready()) { fill(); } if (nextchar < nchars) { if (cb[nextchar] == '\n') nextchar++; skiplf = false; } } return (nextchar < nchars) || in.ready(); } } // 始终返回true。因为bufferedreader支持mark(), reset() public boolean marksupported() { return true; } // 标记当前bufferedreader的下一个要读取位置。关于readaheadlimit的作用,参考后面的说明。 public void mark(int readaheadlimit) throws ioexception { if (readaheadlimit < 0) { throw new illegalargumentexception("read-ahead limit < 0"); } synchronized (lock) { ensureopen(); // 设置readaheadlimit this.readaheadlimit = readaheadlimit; // 保存下一个要读取的位置 markedchar = nextchar; // 保存“是否忽略换行符”标记 markedskiplf = skiplf; } } // 重置bufferedreader的下一个要读取位置, // 将其还原到mark()中所保存的位置。 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; in.close(); in = null; cb = null; } } }
说明:
要想读懂bufferreader的源码,就要先理解它的思想。bufferreader的作用是为其它reader提供缓冲功能。创建bufferreader时,我们会通过它的构造函数指定某个reader为参数。bufferreader会将该reader中的数据分批读取,每次读取一部分到缓冲中;操作完缓冲中的这部分数据之后,再从reader中读取下一部分的数据。
为什么需要缓冲呢?原因很简单,效率问题!缓冲中的数据实际上是保存在内存中,而原始数据可能是保存在硬盘或nandflash中;而我们知道,从内存中读取数据的速度比从硬盘读取数据的速度至少快10倍以上。
那干嘛不干脆一次性将全部数据都读取到缓冲中呢?第一,读取全部的数据所需要的时间可能会很长。第二,内存价格很贵,容量不想硬盘那么大。
下面,我就bufferreader中最重要的函数fill()进行说明。其它的函数很容易理解,我就不详细介绍了,大家可以参考源码中的注释进行理解。我们先看看fill()的源码:
private void fill() throws ioexception { int dst; if (markedchar <= unmarked) { /* no mark */ dst = 0; } else { /* marked */ 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; 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()执行的操作等价于以下代码:
private void fill() throws ioexception { int dst; if (markedchar <= unmarked) { /* no mark */ dst = 0; } int n; 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()执行的操作等价于以下代码:
private void fill() throws ioexception { int dst; if (markedchar > unmarked) { int delta = nextchar - markedchar; if (delta >= readaheadlimit) { markedchar = invalidated; readaheadlimit = 0; dst = 0; } } int n; 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()执行的操作等价于以下代码:
private void fill() throws ioexception { int dst; if (markedchar > unmarked) { int delta = nextchar - markedchar; if ((delta < readaheadlimit) && (readaheadlimit <= cb.length) ) { system.arraycopy(cb, markedchar, cb, 0, delta); markedchar = 0; dst = delta; nextchar = nchars = delta; } } int n; do { n = in.read(cb, dst, cb.length - dst); } while (n == 0); if (n > 0) { nchars = dst + n; nextchar = dst; } }
说明:
这种情况发生的情况是 — — bufferedreader中有很长的数据,我们每次从中读取一部分数据到缓冲区中进行操作。当我们读取完缓冲区中的数据之后,并且此时,bufferedreader存在标记时,同时,“当前标记的长度”小于“标记上限”,并且“标记上限”小于/等于“缓冲区长度”;那么,就发生情况3。此时,我们保留“被标记的位置”(即,保留被标记位置开始的数据),并更新缓冲区(将新增的数据,追加到保留的数据之后)。
情况4:读取完缓冲区的数据,缓冲区的标记位置>0,“当前标记的长度”没超过“标记上限(readaheadlimit)”,并且“标记上限(readaheadlimit)”大于“缓冲的长度”;
执行流程如下,
(01) 其它函数调用 fill(),来更新缓冲区的数据
(02) fill() 执行代码 else { char ncb[] = new char[readaheadlimit]; ... }
为了方便分析,我们将这种情况下fill()执行的操作等价于以下代码:
private void fill() throws ioexception { int dst; if (markedchar > unmarked) { int delta = nextchar - markedchar; if ((delta < readaheadlimit) && (readaheadlimit > cb.length) ) { char ncb[] = new char[readaheadlimit]; system.arraycopy(cb, markedchar, ncb, 0, delta); cb = ncb; markedchar = 0; dst = delta; nextchar = nchars = delta; } } int n; 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):
import java.io.bufferedreader; import java.io.bytearrayinputstream; import java.io.file; import java.io.inputstream; import java.io.filereader; import java.io.ioexception; import java.io.filenotfoundexception; import java.lang.securityexception; /** * bufferedreader 测试程序 * * */ public class bufferedreadertest { private static final int len = 5; public static void main(string[] args) { testbufferedreader() ; } /** * bufferedreader的api测试函数 */ private static void testbufferedreader() { // 创建bufferedreader字符流,内容是arrayletters数组 try { file file = new file("bufferedreader.txt"); bufferedreader in = new bufferedreader( new filereader(file)); // 从字符流中读取5个字符。“abcde” for (int i=0; i<len; i++) { // 若能继续读取下一个字符,则读取下一个字符 if (in.ready()) { // 读取“字符流的下一个字符” int tmp = in.read(); system.out.printf("%d : %c\n", i, tmp); } } // 若“该字符流”不支持标记功能,则直接退出 if (!in.marksupported()) { system.out.println("make not supported!"); return ; } // 标记“当前索引位置”,即标记第6个位置的元素--“f” // 1024对应marklimit in.mark(1024); // 跳过22个字符。 in.skip(22); // 读取5个字符 char[] buf = new char[len]; in.read(buf, 0, len); system.out.printf("buf=%s\n", string.valueof(buf)); // 读取该行剩余的数据 system.out.printf("readline=%s\n", in.readline()); // 重置“输入流的索引”为mark()所标记的位置,即重置到“f”处。 in.reset(); // 从“重置后的字符流”中读取5个字符到buf中。即读取“fghij” in.read(buf, , len); system.out.printf("buf=%s\n", string.valueof(buf)); in.close(); } catch (filenotfoundexception e) { e.printstacktrace(); } catch (securityexception e) { e.printstacktrace(); } catch (ioexception e) { e.printstacktrace(); } } }
程序中读取的bufferedreader.txt的内容如下:
abcdefghijklmnopqrstuvwxyz 0123456789 abcdefghijklmnopqrstuvwxyz
运行结果:
0 : a 1 : b 2 : c 3 : d 4 : e buf=01234 readline=56789 buf=fghij
以上所述是小编给大家介绍的java 中的 bufferedreader 介绍_动力节点java学院整理,希望对大家有所帮助
推荐阅读
-
Java 中的 BufferedReader 介绍_动力节点Java学院整理
-
Java 中的FileReader和FileWriter源码分析_动力节点Java学院整理
-
Java中的FilterOutputStream 简介_动力节点Java学院整理
-
Java 中的 DataInputStream 介绍_动力节点Java学院整理
-
Java中的InputStreamReader和OutputStreamWriter源码分析_动力节点Java学院整理
-
Java设计模式之迭代器模式_动力节点Java学院整理
-
Java设计模式之解释器模式_动力节点Java学院整理
-
Java递归读取文件例子_动力节点Java学院整理
-
Java线程的生命周期和状态控制_动力节点Java学院整理
-
Java HelloWorld原理分析_动力节点Java学院整理