天天看點

Java IO - PushbackInputStream&PushbackReader

基本概念

這裡将 PushbackInputStream 和 PushbackReader 放到一起讨論主要是二者的原理機制幾乎一模一樣,掌握其一即可。它們分别表示 位元組推回流、字元推回流。

退回:其實就是将從流讀取的資料再推回到流中。實作原理也很簡單,通過一個緩沖數組來存放推回的資料,每次操作時先從緩沖數組開始,然後再操作流對象。

繼承關系:

Java IO - PushbackInputStream&PushbackReader
Java IO - PushbackInputStream&PushbackReader

執行個體探究

比如下面的代碼中将[ a,b,c,d] 推回流,也可以将 [x,y,z,u] 推回流,那麼下一次的讀取結果就變成了 [x,y,z,u,h,i,j]。

public class Test {

    // 英文字母
    private static final byte[] ArrayLetters = { 
        , , , , , , , 
        , , , , , , ,
        , , , , , , , 
        , , , , };

    public static void main(String args[]) throws Exception {
        read();
    }

    private static void read() throws IOException {

        //建立位元組推回流,緩沖區大小為 7
        PushbackInputStream pis = new PushbackInputStream(new ByteArrayInputStream(ArrayLetters),);

        byte[] buffer = new byte[];

        //從流中将7個位元組讀入數組,如 a,b,c,d,e,f,g
        pis.read(buffer);
        System.out.println(new String(buffer));

        //從數組的第一個位置開始,推回 4 個位元組到流中,即推回 a,b,c,d
        pis.unread(buffer,,);

        //重新從流中将位元組讀取數組,輸出 a,b,c,d,h,i,j
        pis.read(buffer);
        System.out.println(new String(buffer));
    }
}
           

原理講解

①建立位元組推回流,推回緩沖區指定大小為 7;要從位元組輸入流讀取位元組到 buffer 數組

  • 緩沖區
Java IO - PushbackInputStream&PushbackReader
  • 輸入流,這裡以上面的代碼為例子,位元組數組輸入流中有 26 個位元組(偷懶隻畫 8 個)
Java IO - PushbackInputStream&PushbackReader
  • buffer 數組
Java IO - PushbackInputStream&PushbackReader

②從流中讀取 7 個位元組到 buffer數組

  • 緩沖區
Java IO - PushbackInputStream&PushbackReader
  • 輸入流
Java IO - PushbackInputStream&PushbackReader
  • buffer 數組
Java IO - PushbackInputStream&PushbackReader

③現在要從 buffer 的 0 下标(第一個位置)開始,推回 4 個位元組到流中去(實質是推回到緩沖數組中去)。

  • 緩沖區,位元組被推回到緩沖區,從末端開始存放
Java IO - PushbackInputStream&PushbackReader
  • 輸入流
Java IO - PushbackInputStream&PushbackReader
  • buffer 數組
Java IO - PushbackInputStream&PushbackReader

④再次進行讀取操作,首先從緩沖區開始讀取,再對流進行操作

  • 緩沖區
Java IO - PushbackInputStream&PushbackReader
  • 輸入流 ,後面的資料沒有畫出來
Java IO - PushbackInputStream&PushbackReader
  • buffer 數組
Java IO - PushbackInputStream&PushbackReader

源碼分析

1.PushbackInputStream

類結構圖

Java IO - PushbackInputStream&PushbackReader

成員變量

// 推回緩沖區,推回流的位元組會被儲存在這裡
protected byte[] buf;

// 推回緩沖區中的索引位置,預設從 buf.length 開始,即從數組末尾寫入資料
protected int pos;
           

構造函數,它真正隻做了兩件事:

  • 擷取要操作(要過濾)的流
  • 建立推回緩沖數組
//①構造函數,建立指定大小的退回緩沖區
public PushbackInputStream(InputStream in, int size) {

    //擷取要操作的流
    super(in);

    if (size <= ) {
        throw new IllegalArgumentException("size <= 0");
    }

    //建立推回緩沖區,索引位置預設為 size;
    this.buf = new byte[size];
    this.pos = size;
}

//②構造函數,建立預設大小為 1 的退回緩沖區
public PushbackInputStream(InputStream in) {
    this(in, );
}
           

read 方法,這裡隻定義了兩種讀取方式。

// ①從此輸入流中讀取下一個資料位元組
public int read() throws IOException {
    // 確定要操作的流不為空(即沒有關閉),下面會提到
    ensureOpen();


    // 上面說一開始這兩個參數相等,不相等說明緩沖數組有資料,即表明了進行過推回操作
    if (pos < buf.length) {
        // 預設先從緩沖數組讀取資料
        return buf[pos++] & ;
    }

    //調用流本身的讀取方法
    return super.read();
}


// ②從此輸入流将最多 len 個資料位元組讀入 byte 數組
public int read(byte[] b, int off, int len) throws IOException {

    ensureOpen();

    //判斷參數的合法性
    if (b == null) {
        throw new NullPointerException();
    } else if (off <  || len <  || len > b.length - off) {
        throw new IndexOutOfBoundsException();
    } else if (len == ) {
        return ;
    }

    //判斷 avail 是否為 0,不為 0 表示緩沖數組有推回的位元組
    int avail = buf.length - pos;

    //緩沖區有資料,預設從緩沖區先讀取
    if (avail > ) {

        //判斷要讀取的位元組數量是否小于緩沖區的位元組數量
        if (len < avail) {
            avail = len;
        }

        //利用數組複制,将緩沖區的資料讀入該數組
        System.arraycopy(buf, pos, b, off, avail);

        pos += avail;
        off += avail;
        len -= avail;
    }

    //如果要讀取的位元組數量超出緩沖數組的位元組數量,繼續從流讀取剩餘的位元組
    if (len > ) {
        len = super.read(b, off, len);

        //提前到達 I/O 流末尾,則傳回
        if (len == -) {
            return avail ==  ? - : avail;
        }

        //傳回讀取的位元組數量 = 從緩沖區讀取的位元組數 + 從流讀取的位元組數 
        return avail + len;
    }

    return avail;
}


//確定要操作的流不為空(即沒有關閉)
private void ensureOpen() throws IOException {
    if (in == null) {
        throw new IOException("Stream closed");
    }
}
           

unread 方法,即推回操作,這裡定義了 3 種推回方式。

// ①推回一個位元組
public void unread(int b) throws IOException {

    ensureOpen();

    //上面說過從數組末尾寫入資料,為 0,表示緩沖區已滿
    if (pos == ) {
        throw new IOException("Push back buffer is full");
    }

    //往緩沖區添加位元組,并減少索引位置
    buf[--pos] = (byte) b;
}

// ②推回 byte 數組的某一部分
public void unread(byte[] b, int off, int len) throws IOException {

    ensureOpen();

    //判斷要推回的位元組數量大于剩餘緩沖容量
    if (len > pos) {
        throw new IOException("Push back buffer is full");
    }

    //修改緩沖區的索引位置
    pos -= len;

    //通過數組複制方式,将數組中的位元組 "推回" 到緩沖區
    System.arraycopy(b, off, buf, pos, len);
}

// ③推回一個 byte 數組 
public void unread(byte[] b) throws IOException {
    unread(b, , b.length);
}
           

skip 方法

public long skip(long n) throws IOException {

    ensureOpen();

    if (n <= ) {
        return ;
    }

    long pskip = buf.length - pos;

    //先判斷緩沖區是否有位元組資料可以跳過
    if (pskip > ) {
        if (n < pskip) {
            pskip = n;
        }
        pos += pskip;
        n -= pskip;
    }

    //n 比換緩沖的位元組數大的話,繼續從流裡面跳躍
    if (n > ) {
        pskip += super.skip(n);
    }
    return pskip;
}
           

剩餘方法

public int available() throws IOException {
    ensureOpen();

    //還要加上推回緩沖區的位元組
    return (buf.length - pos) + super.available();
}


public boolean markSupported() {
    return false;
}


public synchronized void mark(int readlimit) {
}


public synchronized void reset() throws IOException {
    throw new IOException("mark/reset not supported");
}


public synchronized void close() throws IOException {
    if (in == null) {
        return;
    }

    in.close();
    in = null;
    buf = null;
}
           

2.PushbackReader

類結構圖,觀察結構圖我們可以發現與 PushbackInputStream 的結構幾乎一緻。不同的是

  • 将位元組數組換乘字元數組
  • ready 方法對應 PushbackInputStream 的 available 方法
Java IO - PushbackInputStream&amp;PushbackReader

因為實作原理一緻,這裡不做探究。唯一有較大不同的是 read 和 unread該類中加了同步代碼塊,隻允許單線程通路。