天天看點

Java IO:BufferedInputStream使用詳解及源碼分析

使用方法

  BufferedInputStream繼承于FilterInputStream,提供緩沖輸入流功能。緩沖輸入流相對于普通輸入流的優勢是,它提供了一個緩沖數組,每次調用read方法的時候,它首先嘗試從緩沖區裡讀取資料,若讀取失敗(緩沖區無可讀資料),則選擇從實體資料源(譬如檔案)讀取新資料(這裡會嘗試盡可能讀取多的位元組)放入到緩沖區中,最後再将緩沖區中的内容部分或全部傳回給使用者.由于從緩沖區裡讀取資料遠比直接從實體資料源(譬如檔案)讀取速度快。

方法介紹

  BufferedInputStream提供的API如下:

//構造方法
BufferedInputStream(InputStream in)
BufferedInputStream(InputStream in, int size)

//下一位元組是否可讀
synchronized int     available()
//關閉
void     close()
//标記, readlimit為mark後最多可讀取的位元組數
synchronized void     mark(int readlimit)
//是否支援mark, true
boolean     markSupported()
//讀取一個位元組
synchronized int     read()
//讀取多個位元組到b
synchronized int     read(byte[] b, int off, int len)
//重置會mark位置
synchronized void     reset()
//跳過n個位元組
synchronized long     skip(long n)
           

使用示例

public void testBufferedInput() {
    try {
        /**
         * 建立輸入流 BufferedInputStream, 緩沖區大小為8
         * buffer.txt内容為
         * abcdefghij
         */
        InputStream in = new BufferedInputStream(new FileInputStream(new File("buff.txt")), );
        /*從位元組流中讀取5個位元組*/
        byte [] tmp = new byte[];
        in.read(tmp, , );
        System.out.println("位元組流的前5個位元組為: " + new String(tmp));
        /*标記測試*/
        in.mark();
        /*讀取5個位元組*/
        in.read(tmp, , );
        System.out.println("位元組流中第6到10個位元組為: " +  new String(tmp));
        /*reset*/
        in.reset();
        System.out.printf("reset後讀取的第一個位元組為: %c" , in.read());
    } catch (Exception e) {
        e.printStackTrace();
    }
}
           

  運作結果如下:

位元組流的前個位元組為: abcde
位元組流中第到個位元組為: fghij
reset後讀取的第一個位元組為: f
           

源碼分析

構造方法

  BufferedInputStream的構造方法有兩個,差別是緩沖區大小設定。

/**
 * Creates a <code>BufferedInputStream</code>
 * and saves its  argument, the input stream
 * <code>in</code>, for later use. An internal
 * buffer array is created and  stored in <code>buf</code>.
 *
 * @param   in   the underlying input stream.
 */
public BufferedInputStream(InputStream in) {
    this(in, DEFAULT_BUFFER_SIZE); //預設8192, 8M
}

/**
 * Creates a <code>BufferedInputStream</code>
 * with the specified buffer size,
 * and saves its  argument, the input stream
 * <code>in</code>, for later use.  An internal
 * buffer array of length  <code>size</code>
 * is created and stored in <code>buf</code>.
 *
 * @param   in     the underlying input stream.
 * @param   size   the buffer size.
 * @exception IllegalArgumentException if {@code size <= 0}.
 */
public BufferedInputStream(InputStream in, int size) {
    super(in);
    if (size <= ) {
        throw new IllegalArgumentException("Buffer size <= 0");
    }
    buf = new byte[size];
}
           

read方法

  read方法有每次讀取一個位元組和一次讀取多個位元組兩種重載。下面主要分析讀取多個位元組的read方法。重點在于fill()方法。

/**
 * Reads bytes from this byte-input stream into the specified byte array,
 * starting at the given offset.
 *
 * <p> This method implements the general contract of the corresponding
 * <code>{@link InputStream#read(byte[], int, int) read}</code> method of
 * the <code>{@link InputStream}</code> class.  As an additional
 * convenience, it attempts to read as many bytes as possible by repeatedly
 * invoking the <code>read</code> method of the underlying stream.  This
 * iterated <code>read</code> continues until one of the following
 * conditions becomes true: <ul>
 *
 * @param      b     destination buffer.
 * @param      off   offset at which to start storing bytes.
 * @param      len   maximum number of bytes to read.
 * @return     the number of bytes read, or <code>-1</code> if the end of the stream has been reached.
 * @exception  IOException  if this input stream has been closed by invoking its {@link #close()} method,
 *                   or an I/O error occurs.
 */
public synchronized int read(byte b[], int off, int len)
        throws IOException
{
    getBufIfOpen(); // Check for closed stream
    if ((off | len | (off + len) | (b.length - (off + len))) < ) {
        throw new IndexOutOfBoundsException();
    } else if (len == ) {
        return ;
    }

    int n = ;
    for (;;) {
        int nread = read1(b, off + n, len - n); //讀取len長度的位元組到b中
        if (nread <= )
            return (n == ) ? nread : n;
        n += nread;
        if (n >= len)
            return n;
        // if not closed but no bytes available, return
        InputStream input = in;
        if (input != null && input.available() <= )
            return n;
    }
}

/**
 * Check to make sure that buffer has not been nulled out due to
 * close; if not return it;
 */
private byte[] getBufIfOpen() throws IOException {
    byte[] buffer = buf;
    if (buffer == null)
        throw new IOException("Stream closed");
    return buffer;
}

/**
 * Read characters into a portion of an array, reading from the underlying
 * stream at most once if necessary.
 */
private int read1(byte[] b, int off, int len) throws IOException {
    int avail = count - pos; //緩沖區中可讀位元組數
    if (avail <= ) { //沒可讀位元組
        /* If the requested length is at least as large as the buffer, and
           if there is no mark/reset activity, do not bother to copy the
           bytes into the local buffer.  In this way buffered streams will
           cascade harmlessly. */
        if (len >= getBufIfOpen().length && markpos < ) { //沒mark并且請求長度大于buff長度
            return getInIfOpen().read(b, off, len); //直接從檔案中讀取,不走緩沖區
        }
        fill(); //修改或者擴充緩沖區
        avail = count - pos; //可讀位元組數
        if (avail <= ) return -;
    }
    int cnt = (avail < len) ? avail : len; //取最小值, 緩沖區中可能沒有足夠可讀的位元組
    System.arraycopy(getBufIfOpen(), pos, b, off, cnt); //複制
    pos += cnt;
    return cnt;
}

/**
 * Fills the buffer with more data, taking into account
 * shuffling and other tricks for dealing with marks.
 * Assumes that it is being called by a synchronized method.
 * This method also assumes that all data has already been read in,
 * hence pos > count.
 */
private void fill() throws IOException {
    /**
     * 填充字元時如果沒有mark标記, 則直接清空緩沖區,然後将輸入流的資料寫入緩沖區
     * 如果有mark标記,則分如下幾種情況
     * 1 普通mark,直接将标記以前的字元用标記以後的字元覆寫,剩餘的空間讀取輸入流的内容填充
     * 2 目前位置pos >= buffer的長度 >= marklimit,說明mark已經失效,直接清空緩沖區,然後讀取輸入流内容
     * 3 buffer長度超出限制,抛出異常
     * 4 marklimit比buffer的長度還大,此時mark還沒失效,則擴大buffer空間
     */
    byte[] buffer = getBufIfOpen();
    if (markpos < )
        pos = ;            /* no mark: throw away the buffer */
    else if (pos >= buffer.length)  /* no room left in buffer */
        if (markpos > ) {  /* can throw away early part of the buffer */
            int sz = pos - markpos;
            System.arraycopy(buffer, markpos, buffer, , sz);
            pos = sz;
            markpos = ;
        } else if (buffer.length >= marklimit) {
            markpos = -;   /* buffer got too big, invalidate mark */
            pos = ;        /* drop buffer contents */
        } else if (buffer.length >= MAX_BUFFER_SIZE) {
            throw new OutOfMemoryError("Required array size too large");
        } else {            /* grow buffer */
            int nsz = (pos <= MAX_BUFFER_SIZE - pos)
                    pos *  : MAX_BUFFER_SIZE; //擴大後的大小
            if (nsz > marklimit)
                nsz = marklimit;
            byte nbuf[] = new byte[nsz];
            System.arraycopy(buffer, , nbuf, , pos); //将buffer的資料複制到nbuf中
            if (!bufUpdater.compareAndSet(this, buffer, nbuf)) {
                // Can't replace buf if there was an async close.
                // Note: This would need to be changed if fill()
                // is ever made accessible to multiple threads.
                // But for now, the only way CAS can fail is via close.
                // assert buf == null;
                throw new IOException("Stream closed");
            }
            buffer = nbuf; //修改緩沖區
        }
    count = pos;
    int n = getInIfOpen().read(buffer, pos, buffer.length - pos); //讀取輸入流中内容填充緩沖區
    if (n > )
        count = n + pos;
}
           

mark\reset方法

/**
 * See the general contract of the <code>mark</code>
 * method of <code>InputStream</code>.
 *
 * @param   readlimit   the maximum limit of bytes that can be read before
 *                      the mark position becomes invalid.
 * @see     java.io.BufferedInputStream#reset()
 */
public synchronized void mark(int readlimit) {
    marklimit = readlimit;
    markpos = pos;
}

/**
 * See the general contract of the <code>reset</code>
 * method of <code>InputStream</code>.
 * <p>
 * If <code>markpos</code> is <code>-1</code>
 * (no mark has been set or the mark has been
 * invalidated), an <code>IOException</code>
 * is thrown. Otherwise, <code>pos</code> is
 * set equal to <code>markpos</code>.
 *
 * @exception  IOException  if this stream has not been marked or,
 *                  if the mark has been invalidated, or the stream
 *                  has been closed by invoking its {@link #close()}
 *                  method, or an I/O error occurs.
 * @see        java.io.BufferedInputStream#mark(int)
 */
public synchronized void reset() throws IOException {
    getBufIfOpen(); // Cause exception if closed
    if (markpos < )
        throw new IOException("Resetting to invalid mark");
    pos = markpos;
}
           

參考:

[1] http://zhhphappy.iteye.com/blog/1562427

[2] http://blog.sina.com.cn/s/blog_67f995260101huxz.html

[3] http://www.cnblogs.com/skywang12345/p/io_12.html