Java 日看一类(4)之IO包中的BufferedReader类

所引入的包和继承的类:

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类设计思路大同小异,希望读者可以举一反三


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值