在上一篇文章中講了FileInputStream
本文将介紹一個特殊的輸入位元組流:FilterInputStream,以及與之相随的一個經典的設計模式,裝飾者模式。
在之前的文章中提到,InputStream的讀取是以byte為機關的,但是我們日常中經常會讀寫其他類型的資料,當然,我們可以把讀取出來的位元組進行轉碼,轉成我們需要的資料,那麼能不能直接讀取字元,int等資料呢?能,隻需要利用DataInputStream這個類。
1 FilterInputStream 剖析
FilterInputStream是InputStream一個特殊的子類,關于它和InputStream的關系,可以參照這篇文章java io -- InputStream,它有一個很重要filed:
protected volatile InputStream in;
而這個類的特殊之處,就是包含了一個InputStream,使得可以在這個InputStream基礎上進行多種封裝,進而達到裝飾的目的。
上一張這個類的結構圖如下:
結構圖有些說不太清楚,我這裡把簡略後的源碼展示出來:
package java.io;
public class FilterInputStream extends InputStream {
/**
* The input stream to be filtered.
*/
protected volatile InputStream in;
/**
* 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 FilterInputStream(InputStream in) {
this.in = in;
}
/**
* Reads the next byte of data from this input stream. The value
* byte is returned as an <code>int</code> in the range
* <code>0</code> to <code>255</code>. If no byte is available
* because the end of the stream has been reached, the value
* <code>-1</code> is returned. This method blocks until input data
* is available, the end of the stream is detected, or an exception
* is thrown.
* <p>
* This method
* simply performs <code>in.read()</code> and returns the result.
*
* @return the next byte of data, or <code>-1</code> if the end of the
* stream is reached.
* @exception IOException if an I/O error occurs.
* @see java.io.FilterInputStream#in
*/
public int read() throws IOException {
return in.read();
}
/**
* Reads up to <code>byte.length</code> bytes of data from this
* input stream into an array of bytes. This method blocks until some
* input is available.
* <p>
* This method simply performs the call
* <code>read(b, 0, b.length)</code> and returns
* the result. It is important that it does
* <i>not</i> do <code>in.read(b)</code> instead;
* certain subclasses of <code>FilterInputStream</code>
* depend on the implementation strategy actually
* used.
*
* @param b the buffer into which the data is read.
* @return the total number of bytes read into the buffer, or
* <code>-1</code> if there is no more data because the end of
* the stream has been reached.
* @exception IOException if an I/O error occurs.
* @see java.io.FilterInputStream#read(byte[], int, int)
*/
public int read(byte b[]) throws IOException {
return read(b, 0, b.length);
}
/**
* Reads up to <code>len</code> bytes of data from this input stream
* into an array of bytes. If <code>len</code> is not zero, the method
* blocks until some input is available; otherwise, no
* bytes are read and <code>0</code> is returned.
* <p>
* This method simply performs <code>in.read(b, off, len)</code>
* and returns the result.
*
* @param b the buffer into which the data is read.
* @param off the start offset in the destination array <code>b</code>
* @param len the maximum number of bytes read.
* @return the total number of bytes read into the buffer, or
* <code>-1</code> if there is no more data because the end of
* the stream has been reached.
* @exception NullPointerException If <code>b</code> is <code>null</code>.
* @exception IndexOutOfBoundsException If <code>off</code> is negative,
* <code>len</code> is negative, or <code>len</code> is greater than
* <code>b.length - off</code>
* @exception IOException if an I/O error occurs.
* @see java.io.FilterInputStream#in
*/
public int read(byte b[], int off, int len) throws IOException {
return in.read(b, off, len);
}
/**
* Closes this input stream and releases any system resources
* associated with the stream.
* This
* method simply performs <code>in.close()</code>.
*
* @exception IOException if an I/O error occurs.
* @see java.io.FilterInputStream#in
*/
public void close() throws IOException {
in.close();
}
}
從源碼中可以看出,這個FilterInputStream中有一個域:
protected volatile InputStream in;
這個域是在構造方法中傳入的:
protected FilterInputStream(InputStream in) {
this.in = in;
}
這也就說明了FilterInputStream在執行個體化的時候,要傳一個InputStream類的對象進來。
而且這個類中的read方法并不像FileInputStream進行了實作,而隻是一種“僞”實作:
public int read() throws IOException {
return in.read();
}
其實隻是用了這個構造方法傳入的這個InputStream的read方法。
上面隻是介紹了一下這個類,那麼在java中,這個FilterInputStream有什麼作用呢?這就要從它的幾個子類說起了,我再上一張FilterInputStream的類圖,先看看它的幾個子類。
FilterInpustStream子類可以分成兩類:
1) DataInputStream能以一種與機器無關的方式,直接從地從位元組輸入流讀取JAVA基本類型和String類型的資料。
2 )其它的子類使得能夠對InputStream進行改進,即在原有的InputStream基礎上可以提供了新的功能特性。日常中用的最多的就是ButtferInputStream,使得inputStream具有緩沖的功能。
接下來我們就以DataInputStream和BufferInputStream對此進行深入剖析。
DataInputStream
之前的InputStream我們隻能讀取byte,這個類使得我們可以直接從stream中讀取int,String等類型。先把這個類幾個方法及說明列出來如下(部分):
Method Summary
Methods | |
Modifier and Type | Method and Description |
int | read(byte[] b) Reads some number of bytes from the contained input stream and stores them into the buffer array b. |
int | read(byte[] b, int off, int len) Reads up to len bytes of data from the contained input stream into an array of bytes. |
boolean | readBoolean() See the general contract of the readBoolean method of DataInput. |
byte | readByte() See the general contract of the readByte method of DataInput. |
char | readChar() See the general contract of the readChar method of DataInput. |
void | readFully(byte[] b, int off, int len) See the general contract of the readFully method of DataInput. |
int | readInt() See the general contract of the readInt method of DataInput. |
String | readUTF() See the general contract of the readUTF method of DataInput. |
static String | readUTF(DataInput in) Reads from the stream in a representation of a Unicode character string encoded in modified UTF-8 format; this string of characters is then returned as a String. |
有了DataInputStream後,我們就可以直接讀取int,boolean了,下面有一個例子,簡單說明DataInputStream的使用。
try {
DataOutputStream out = new DataOutputStream(new FileOutputStream("/home/zhaohui/tmp/readPrim"));
out.writeInt(123);
out.writeUTF("你好");
out.writeBoolean(true);
out.flush();
out.close();
DataInputStream in = new DataInputStream(new FileInputStream("/home/zhaohui/tmp/readPrim"));
int a = in.readInt();
System.out.println("first int is "+a);
String b = in.readUTF();
System.out.println("second string is "+b);
boolean c= in.readBoolean();
System.out.println("third boolean is "+c);
in.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
輸出:
first int is 123
second string is 你好
third boolean is true
在例子中,我們将一個FileInputStream傳入到DataInputStream中,進而使得我們可以直接對檔案讀取寫入int,或者boolean等。
DataInputStream原理
所有的輸入流都是對byte的操作,這個類能直接讀取int,說明内部一定是對byte進行了處理,我們看一下readInt()方法的源碼。
public final int readInt() throws IOException {
int ch1 = in.read();
int ch2 = in.read();
int ch3 = in.read();
int ch4 = in.read();
if ((ch1 | ch2 | ch3 | ch4) < 0)
throw new EOFException();
return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0));
}
正如我在前一篇文章中所說,java每個int是4個位元組,而InputStream的read是面向位元組的,也就是每次隻能讀取1個位元組,是以在readInt這個方法中,讀取出4個位元組,再進行處理成一個int,這裡我們不對處理過程進行深究。
對于其他的方法思想大緻一樣,不過由于對于String類型需要對字元進行編碼,對字元的長度進行傳遞,會複雜一點,這裡就不多說了,關鍵是這個類算是對InputStream的一個封裝。
BufferedInputStream
我這裡再介紹另一個常用的FilterInputStream類,BufferedInputStream類。
這個類提供了一個緩存來加速我們從輸入流的讀取。
由于我們從InputStream中讀取資料時,一般都會用到os的io,或者網絡io,這都是會耗費大量時間的操作,比如我們現在從檔案讀取前20個位元組,過一會又從檔案讀取20個位元組,這就是兩次io,好的,有了BufferedInputStream,就解決這個兩次io的問題,這個類在read時,幹脆多讀一部分資料進來,放在記憶體裡,等你每次操作流的時候,讀取的資料直接從記憶體中就可以拿到,這就減少了io次數,加快我們的io。
我這裡隻解析BufferedInputStream中的read()方法,有興趣的可以在jdk裡檢視其他部分。
public class BufferedInputStream extends FilterInputStream {
//....省略部分源碼
private static int DEFAULT_BUFFER_SIZE = 8192;
private static int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8;
protected volatile byte buf[];
public synchronized int read() throws IOException {
if (pos >= count) {
fill();
if (pos >= count)
return -1;
}
return getBufIfOpen()[pos++] & 0xff;
}
private void fill() throws IOException {
byte[] buffer = getBufIfOpen();
if (markpos < 0)
pos = 0; /* no mark: throw away the buffer */
else if (pos >= buffer.length) /* no room left in buffer */
if (markpos > 0) { /* can throw away early part of the buffer */
int sz = pos - markpos;
System.arraycopy(buffer, markpos, buffer, 0, sz);
pos = sz;
markpos = 0;
} else if (buffer.length >= marklimit) {
markpos = -1; /* buffer got too big, invalidate mark */
pos = 0; /* 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 * 2 : MAX_BUFFER_SIZE;
if (nsz > marklimit)
nsz = marklimit;
byte nbuf[] = new byte[nsz];
System.arraycopy(buffer, 0, nbuf, 0, pos);
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 > 0)
count = n + pos;
}
}
從上面的代碼中看到,這個類有一個buf[],也就是用來當做緩存的位元組數組。每次調用read讀取資料時,先檢視要讀取的資料是否在緩存中,如果在緩存中,直接從緩存中讀取;如果不在緩存中,則調用fill方法,從InputStream中讀取一定的存儲到buf中。
是以利用BufferedInputStream讀取資料時,在一定的情況下是可以加速的。
裝飾者模式
在講了FilterInputStream之後,就必須要提到一個設計模式:裝飾者模式(decorator pattern)。
我這裡不是專門講解裝飾者模式的,隻是說明裝飾者模式在流裡的應用。
裝飾者模式,顧名思義,是對原有類進行了一定的裝飾,裝飾後的類必須和原有的類擁有相同的方法,當然,可以在原有類的基礎上進行擴充。
這裡的裝飾者模式通過包含一個原有的Inputstream對象,并且将InputStream原有的方法或直接暴露,或進行裝飾後暴露,又或者添加了新的特性,如DataInputStream中的readInt(),BufferedInputStream中的緩存功能。
其實這裡還有一個話題,為什麼InputStream選擇裝飾者模式,而非直接繼承的方法來擴充,這就是裝飾者模式VS繼承。
為了回答這個問題,如果我用了繼承,看看我們的類圖是什麼樣的。
如圖所示,我展示的還隻是一部分類圖,如果單純的使用繼承,就會造成類的“爆炸”式增長。
我在這裡做個簡單的分析,為什麼造成這種爆炸式的增長。
在InputStream的直接子類中,如FileInputStream,都是定義了輸入流的<來源>,或者說是媒體,通過檔案,或者網絡。而FilterInputStream的子類并不是增加了流的來源,而隻是改善了流讀取方法,比如添加了緩存,直接讀取int,String等類型。
可以這樣簡單的認為,InputStream的直接子類是“目的”,而FilterInpustStream的子類是“方法”,我們用一個InputStream就是要用目的和方法。
直接使用繼承,可以實作“目的”和“方法”,但是每一種來源的輸入流,都需要改善流讀取方法,是以在使用繼承時,每一個InputStream的子類都需要DataInputStream,BufferedInputStream這幾個類提供的“裝飾作用”的功能,是以需要的類的數目就是A*B的數目。
而直接使用裝飾者模式,将InputStream的幾個直接子類進一步抽象,在此基礎上提供裝飾作用,所需要的類的數目是A+B。使用裝飾者模式使得java類的更有層次性,類的數目得到充分控制。這就是裝飾者模式相比于繼承的優勢。