Java的IO標準庫提供的InputStream根據來源可以包括:
- FileInputStream:從文件讀取數據,是最終數據源
- ServletInputStream:從HTTP請求讀取數據,是最終數據源
- Socket.getInputStream():從TCP連接讀取數據,是最終數據源
- …
如果我們要給FileInputStream添加緩衝功能,則可以從FileInputStream派生一個類:
BufferedFileInputStream extends FileInputStream
如果要給FileInputStream添加計算簽名的功能,類似的,也可以從FileInputStream派生一個類:
DigestFileInputStream extends FileInputStream
如果要給FileInputStream添加加密/解密功能,還是可以從FileInputStream派生一個類:
CipherFileInputStream extends FileInputStream
如果要給FileInputStream添加緩衝和簽名的功能,那麼我們還需要派生BufferedDigestFileInputStream。如果要給FileInputStream添加緩衝和加解密的功能,則需要派生BufferedCipherFileInputStream。
我們發現,給FileInputStream添加3種功能,至少需要3個子類。這3種功能的組合,又需要更多的子類:
這還只是針對FileInputStream設計,如果針對另一種InputStream設計,很快會出現子類爆炸的情況。
因此,直接使用繼承,爲各種InputStream附加更多的功能,根本無法控制代碼的複雜度,很快就會失控。
爲了解決依賴繼承會導致子類數量失控的問題,JDK首先將InputStream分爲兩大類:
一類是直接提供數據的基礎InputStream,例如:
- FileInputStream
- ByteArrayInputStream
- ServletInputStream
- …
一類是提供額外附加功能的InputStream,例如:
- BufferedInputStream
- DigestInputStream
- CipherInputStream
- …
當我們需要給一個“基礎”InputStream附加各種功能時,我們先確定這個能提供數據源的InputStream,因爲我們需要的數據總得來自某個地方,例如,FileInputStream,數據來源自文件:
InputStream file = new FileInputStream("test.gz");
緊接着,我們希望FileInputStream能提供緩衝的功能來提高讀取的效率,因此我們用BufferedInputStream包裝這個InputStream,得到的包裝類型是BufferedInputStream,但它仍然被視爲一個InputStream:
InputStream buffered = new BufferedInputStream(file);
最後,假設該文件已經用gzip壓縮了,我們希望直接讀取解壓縮的內容,就可以再包裝一個GZIPInputStream:
InputStream gzip = new GZIPInputStream(buffered);
無論我們包裝多少次,得到的對象始終是InputStream,我們直接用InputStream來引用它,就可以正常讀取:
上述這種通過一個“基礎”組件再疊加各種“附加”功能組件的模式,稱之爲Filter模式(或者裝飾器模式:Decorator)。它可以讓我們通過少量的類來實現各種功能的組合:
類似的,OutputStream也是以這種模式來提供各種功能:
編寫FilterInputStream
我們也可以自己編寫FilterInputStream,以便可以把自己的FilterInputStream“疊加”到任何一個InputStream中。
下面的例子演示瞭如何編寫一個CountInputStream,它的作用是對輸入的字節進行計數:
注意到在疊加多個FilterInputStream,我們只需要持有最外層的InputStream,並且,當最外層的InputStream關閉時(在try(resource)塊的結束處自動關閉),內層的InputStream的close()方法也會被自動調用,並最終調用到最核心的“基礎”InputStream,因此不存在資源泄露。
import java.io.ByteArrayInputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
public class Demo04 {
public static void main(String[] args) throws IOException {
byte[] data = "hello, world!".getBytes("UTF-8");
try (CountInputStream input = new CountInputStream(new ByteArrayInputStream(data))) {
int n;
while ((n = input.read()) != -1) {
System.out.println((char)n);
}
System.out.println("Total read " + input.getBytesRead() + " bytes");
}
}
}
class CountInputStream extends FilterInputStream{
private int count=0;
/**
* Creates a <code>FilterInputStream</code>
* by assigning the argument <code>in</code>
* to the field <code>this.in</code> so as
* to remember it for later use.
*
* @param in the underlying input stream, or <code>null</code> if
* this instance is to be created without an underlying stream.
*/
protected CountInputStream(InputStream in) {
super(in);
}
public int getBytesRead(){
return this.count;
}
public int read() throws IOException{
int n=in.read();
if (n!=-1){
this.count++;
}
return n;
}
public int read(byte[] b,int off,int len) throws IOException{
int n=in.read(b,off,len);
this.count+=n;
return n;
}
}
小結
Java的IO標準庫使用Filter模式爲InputStream和OutputStream增加功能:
可以把一個InputStream和任意個FilterInputStream組合;
可以把一個OutputStream和任意個FilterOutputStream組合。
Filter模式可以在運行期動態增加功能(又稱Decorator模式)。
謝謝觀看