所引入的包和继承的类:
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;
该类继承自Reader类
类的使用注释:
/** * Reads text from a character-input stream, buffering characters so as to * provide for the efficient reading of characters, arrays, and lines. * * <p> The buffer size may be specified, or the default size may be used. The * default is large enough for most purposes. * * <p> In general, each read request made of a Reader causes a corresponding * read request to be made of the underlying character or byte stream. It is * therefore advisable to wrap a BufferedReader around any Reader whose read() * operations may be costly, such as FileReaders and InputStreamReaders. For * example, * * <pre> * BufferedReader in * = new BufferedReader(new FileReader("foo.in")); * </pre> * * will buffer the input from the specified file. Without buffering, each * invocation of read() or readLine() could cause bytes to be read from the * file, converted into characters, and then returned, which can be very * inefficient. * * <p> Programs that use DataInputStreams for textual input can be localized by * replacing each DataInputStream with an appropriate BufferedReader. * * @see FileReader * @see InputStreamReader * @see java.nio.file.Files#newBufferedReader * * @author Mark Reinhold * @since JDK1.1 */
大意如下:
从一个输入的字符流中读取文本,为字符、数组、一行文本的高效读取提供字符缓冲功能。
缓冲区的大小可能是特殊设定值,可能是使用默认的大小。默认的大小已经足够解决大部分问题。
一般情况下,每一个read请求由一个Reader类发起,使底层进行对字节流或byte流执行相应的读取请求。
对一些read()函数开销较大的的Reader类,(例如:FileReaders和InputStreamReaders)使用BufferedReader类进行封装是很明智的。
引例:
BufferedReader in = new BufferedReader(new FileReader("foo.in"));
如果没由进行缓冲,对read()和readline()调用会直接从文件中读取一次byte数据,转换成字符格式并返回,这是十分不明智的做法。
程序对文本数据使用DataInputStream时,在合适的情况下可以局部替换DataInputStream为BufferedReader
BufferedReader类含有十一个成员变量:
内部包含的Reader对象:
private Reader in;
缓冲区数组:
private char cb[];
缓冲区数组的标识符:
private int nChars, nextChar;
无效数据标识:
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 */
是否跳过下个换行符:
private boolean skipLF = false;
是否跳过所有的换行符:
private boolean markedSkipLF = false;
默认缓冲区大小:
private static int defaultCharBufferSize = 8192;
默认预计行长度:
private static int defaultExpectedLineLength = 80;
含有十五个方法:
构造函数(自定义缓冲区大小):(传入一个Reader类和int值)
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; }
构造函数(默认缓冲区大小):
public BufferedReader(Reader in) { this(in, defaultCharBufferSize); }
判定流的有效性:
private void ensureOpen() throws IOException { if (in == null) throw new IOException("Stream closed"); }
缓冲区装填:
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;//标记位置为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);//当实际读取字节数为0时继续读取 if (n > 0) { nChars = dst + n;//读取成功修改有效字节数 nextChar = dst;//修改读取指针指向 } }
从缓冲区中读取数据:
public int read() throws IOException { synchronized (lock) {//线程枷锁 ensureOpen(); for (;;) { if (nextChar >= nChars) {//读取指针超出有效字符数 fill();//更新缓冲区 if (nextChar >= nChars)//仍超出有效字符数 return -1;//程序报错返回 } if (skipLF) {//设定为跳过下个换行符 skipLF = false;//标记重置 if (cb[nextChar] == '\n') {//下个字符为换行符 nextChar++; continue;//直接跳过并继续循环 } } return cb[nextChar++];//返回读取值 } } }
缓冲区大批量读取(内部私有):
private int read1(char[] cbuf, int off, int len) throws IOException { if (nextChar >= nChars) {//读取指针超出目前有效字符数 /* If the requested length is at least as large as the buffer, and if there is no mark/reset activity, and if line feeds are not being skipped, do not bother to copy the characters into the local buffer. In this way buffered streams will cascade harmlessly. */ if (len >= cb.length && markedChar <= UNMARKED && !skipLF) {//需读取长度大于缓冲区大小且不存在标记且不需要跳过下个换行符 return in.read(cbuf, off, len);//直接从Reader中读取,不经过缓冲区 } fill(); } if (nextChar >= nChars) return -1;//刷新后仍超出则程序报错 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;//返回有效读取长度 }
缓冲区批量读取(公有):
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)) {//读取指针小于0或需读取长度大于传入数组长度或需读取长度小于0或读取指针预需读取长度相加大于数组长度或相加小于0 throw new IndexOutOfBoundsException();//数组越界 } else if (len == 0) {//读取长度为0 return 0; } int n = read1(cbuf, off, len);//调用上个函数,得到实际读取长度 if (n <= 0) return n;//实际读取长度小于等于0,直接返回(返回0或报错) while ((n < len) && in.ready()) {//当n小于需读取长度且Reader流准备好时 int n1 = read1(cbuf, off + n, len - n);//直接从Reader流中读取剩余所需的数据 if (n1 <= 0) break; n += n1; } return n; } }
行文本读取(当行文本过大时会创建一个字符串缓冲区单独储存):
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)//字符串缓冲区不为空且长度大于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') {//若charloop因回车中断,跳过下一个换行符 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); }
跳过后续的字符(在缓冲区中操作):
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(); /* * 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) {//最大预读小于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; } } }
返回字符串流:
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);//未检查IO异常 } } } @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);//返回一个有定制操作迭代器的String流 }
Buffered型的IO类设计思路大同小异,希望读者可以举一反三