基本概念
這裡将 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 數組
- 緩沖區
- 輸入流,這裡以上面的代碼為例子,位元組數組輸入流中有 26 個位元組(偷懶隻畫 8 個)
- buffer 數組
②從流中讀取 7 個位元組到 buffer數組
- 緩沖區
- 輸入流
- buffer 數組
③現在要從 buffer 的 0 下标(第一個位置)開始,推回 4 個位元組到流中去(實質是推回到緩沖數組中去)。
- 緩沖區,位元組被推回到緩沖區,從末端開始存放
- 輸入流
- buffer 數組
④再次進行讀取操作,首先從緩沖區開始讀取,再對流進行操作
- 緩沖區
- 輸入流 ,後面的資料沒有畫出來
- buffer 數組
源碼分析
1.PushbackInputStream
類結構圖
成員變量
// 推回緩沖區,推回流的位元組會被儲存在這裡
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 方法
因為實作原理一緻,這裡不做探究。唯一有較大不同的是 read 和 unread該類中加了同步代碼塊,隻允許單線程通路。