1. 程式人生 > >簡述JAVA IO流以及IO流中的介面卡模式、裝飾模式

簡述JAVA IO流以及IO流中的介面卡模式、裝飾模式

摘要:讀完本章節,您對java 的IO流有更清晰深刻的認識,對介面卡模式、裝飾模式也有初步的瞭解。

        一、關於流引用百度百科上的解釋:

        流是一種抽象概念,它代表了資料的無結構化傳遞。按照流的方式進行輸入輸出,資料被當成無結構的位元組序或字元序列。從流中取得資料的操作稱為提取操作,亦稱讀操作;而向流中新增資料的操作稱為插入操作,亦稱寫操作。用來進行輸入輸出操作的流就稱為IO流。換句話說說,IO流就是以流的方式進行輸入輸出。應用場景如Java Web上的檔案上傳與下載、磁碟檔案的讀操作寫操作,這些都需要用到流。

       二、流的分類:

        按流的流向分為輸入流、輸出流,InputStream、Reader及其所有子類都是輸入流,OutputStream、Writer及其所有子類都是輸出流,從流中讀資料用輸入流,寫資料到流中用輸出流。

        按流的資料單位分為位元組流、字元流,InputStream、OutputStream及其所有子類都是位元組流,Reader、Writer及其所有子類都是字元流,當操作流中的資料含有中文的時候,請務必使用字元流進行讀寫。如果流中含有中文而使用位元組流進行讀寫有可能出現中文亂碼、資料失真現象,因為資料如果是GBK編碼一箇中文漢字含有兩個位元組,如果是UTF-8編碼一箇中文漢字含有三個位元組,讀寫的時候如若處理不當,很容易產生中文亂碼。

       按流的功能分為節點流、處理流(過濾流),節點流是指從特定地方讀寫的流類如磁碟或者一塊記憶體區域記憶體,比如ByteArrayInputStream、FileInputStream等,處理流是指通過已經存在的輸入輸出流構造的流,比如BufferedInputStream、DataInputStream等

        三、輸入流詳解

        a、位元組輸入流InputStream及其子類,

             InputStream主要方法:

            1、public abstract int read() throws IOException;

           從輸入流中讀取資料的下一個位元組。返回0到255範圍內的int值。如果因為已經到達流末尾而沒有可用的位元組, 則返回-1。在輸入資料可用、檢測到流末尾或者丟擲異常前,此方法一直阻塞。子類必須提供此方法的實現。

            2、public int read(byte b[]) throws IOException {...};

           從輸入流中讀取一定數量的位元組,並將其儲存在緩衝區陣列b中。以整數形式返回實際讀取的位元組個數。在輸入資料可用、檢測到檔案末尾或者丟擲異常前,此方法一直阻塞。如果陣列b的長度為0,則不讀取任何位元組並返回0;否則,嘗試讀取至少一個位元組。如果因為流位於檔案末尾而沒有可用的位元組,則返回值-1;否則,至少讀取一個位元組並將其儲存在b中。將讀取的第一個位元組儲存在元素b[0]中,下一個儲存在b[1]中,以此類推。讀取的位元組數最多等於b的長度。設k為實際讀取的位元組數;這些位元組將儲存在b[0]到b[k-1]中,不影響b[k]到b[b.length-1]的元素。類InputStream的read(b)方法的效果等同於read(b,0,b.length)。

             3、public int read(byte b[], int off, int len) throws IOException {...};

              將輸入流中最多 len 個數據位元組讀入 byte 陣列。嘗試讀取 len 個位元組,但讀取的位元組也可能小於該值。以整數形式返回實際讀取的位元組數。在輸入資料可用、檢測到流末尾或者丟擲異常前,此方法一直阻塞。如果 len 為 0,則不讀取任何位元組並返回 0;否則,嘗試讀取至少一個位元組。如果因為流位於檔案末尾而沒有可用的位元組,則返回值 -1;否則,至少讀取一個位元組並將其儲存在 b 中。將讀取的第一個位元組儲存在元素 b[off] 中,下一個儲存在 b[off+1] 中,依次類推。讀取的位元組數最多等於 len。設 k 為實際讀取的位元組數;這些位元組將儲存在 b[off]b[off+k-1] 的元素中,不影響 b[off+k]b[off+len-1] 的元素。在任何情況下,b[0]b[off] 的元素以及 b[off+len]b[b.length-1] 的元素都不會受到影響。類 InputStreamread(b, off, len) 方法重複呼叫方法 read()。如果第一次這樣的呼叫導致 IOException,則從對 read(b, off, len) 方法的呼叫中返回該異常。如果對 read() 的任何後續呼叫導致 IOException,則捕獲該異常並將其視為到達檔案末尾;到達該點時讀取的位元組儲存在 b 中,並返回發生異常之前讀取的位元組數。在已讀取輸入資料 len 的請求數量、檢測到檔案結束標記、丟擲異常前,此方法的預設實現將一直阻塞。建議子類提供此方法更為有效的實現

       4、public long skip(long n) throws IOException {...};

       跳過和丟棄此輸入流中資料的 n 個位元組。出於各種原因,skip 方法結束時跳過的位元組數可能小於該數,也可能為 0。導致這種情況的原因很多,跳過 n 個位元組之前已到達檔案末尾只是其中一種可能。返回跳過的實際位元組數。如果 n 為負,則不跳過任何位元組。此類的 skip 方法建立一個 byte 陣列,然後重複將位元組讀入其中,直到讀夠 n 個位元組或已到達流末尾為止。建議子類提供此方法更為有效的實現。例如,可依賴搜尋能力的實現。

        5、public int available() throws IOException {...};

        返回此輸入流下一個方法呼叫可以不受阻塞地從此輸入流讀取(或跳過)的估計位元組數,即返回此輸入流未讀取位元組數。下一個呼叫可能是同一個執行緒,也可能是另一個執行緒。一次讀取或跳過此估計數個位元組不會受阻塞,但讀取或跳過的位元組數可能小於該數。注意,有些 InputStream 的實現將返回流中的位元組總數,但也有很多實現不會這樣做。試圖使用此方法的返回值分配緩衝區,以儲存此流所有資料的做法是不正確的。如果已經呼叫 close() 方法關閉了此輸入流,那麼此方法的子類實現可以選擇丟擲 IOExceptionInputStreamavailable 方法總是返回 0。此方法應該由子類重寫。

         6、public void close() throws IOException {};

         關閉此輸入流並釋放與該流關聯的所有系統資源。InputStreamclose 方法不執行任何操作。

         7、public synchronized void mark(int readlimit) {};

         在此輸入流中標記當前的位置。對 reset 方法的後續呼叫會在最後標記的位置重新定位此流,以便後續讀取重新讀取相同的位元組。readlimit 引數告知此輸入流在標記位置失效之前允許讀取的位元組數。mark 的常規協定是:如果方法 markSupported 返回 true,那麼輸入流總是在呼叫 mark 之後記錄所有讀取的位元組,並時刻準備在呼叫方法 reset 時(無論何時),再次提供這些相同的位元組。但是,如果在呼叫 reset 之前可以從流中讀取多於 readlimit 的位元組,則不需要該流記錄任何資料。標記已關閉的流對其無效。InputStreammark 方法不執行任何操作。

          8、public synchronized void reset() throws IOException {...};

          將此流重新定位到最後一次對此輸入流呼叫 mark 方法時的位置。除了丟擲 IOException 之外,類 InputStream 的方法 reset 不執行任何操作。

           9、public boolean markSupported() {...};

        測試此輸入流是否支援 markreset 方法。是否支援 markreset 是特定輸入流例項的不變屬性。InputStreammarkSupported 方法返回 false。如果此輸入流例項支援 mark 和 reset 方法,則返回 true;否則返回 false

          InputStream子類:

          1、ByteArrayInputStream

          包含一個內部緩衝區,該緩衝區包含從流中讀取的位元組。內部計數器跟蹤 read 方法要提供的下一個位元組。關閉 ByteArrayInputStream 無效。此類中的方法在關閉此流後仍可被呼叫,而不會產生任何 IOException

             欄位以及構造方法描述,其他方法功能描述與父類InputStream一致,不再說明。截圖自JDK API1.6文件(下同):

                            

       2、FileInputStream

        從檔案系統中的某個檔案中獲得輸入位元組。哪些檔案可用取決於主機環境。用於讀取諸如影象資料之類的原始位元組流。要讀取字元流,請考慮使用 FileReader

                           

           3、FilterInputStream

          包含其他一些輸入流,它將這些流用作其基本資料來源,它可以直接傳輸資料或提供一些額外的功能。FilterInputStream 類本身只是簡單地重寫那些將所有請求傳遞給所包含輸入流的 InputStream 的所有方法。FilterInputStream 的子類可進一步重寫這些方法中的一些方法,並且還可以提供一些額外的方法和欄位。FilterInputStream子類有BufferedInputStream、DataInputStream、PustbackInputStream。                       

         3.1、BufferedInputStream

         為另一個輸入流新增一些功能,即緩衝輸入以及支援 markreset 方法的能力。在建立 BufferedInputStream 時,會建立一個內部緩衝區陣列。在讀取或跳過流中的位元組時,可根據需要從包含的輸入流再次填充該內部緩衝區,一次填充多個位元組。mark 操作記錄輸入流中的某個點,reset 操作使得在從包含的輸入流中獲取新位元組之前,再次讀取自最後一次 mark 操作後讀取的所有位元組。

                          

        3.2、DataInputStream

         資料輸入流允許應用程式以與機器無關方式從底層輸入流中讀取基本 Java 資料型別。應用程式可以使用資料輸出流寫入稍後由資料輸入流讀取的資料。DataInputStream 對於多執行緒訪問不一定是安全的。

         3.3、PustbackInputStream

          為另一個輸入流新增效能,即“推回 (push back)”或“取消讀取 (unread)”一個位元組的能力。在程式碼片段可以很方便地讀取由特定位元組值分隔的不定數量的資料位元組時,這很有用;在讀取終止位元組後,程式碼片段可以“取消讀取”該位元組,這樣,輸入流上的下一個讀取操作將會重新讀取被推回的位元組。例如,表示構成識別符號字元的位元組可能由表示操作符字元的位元組終止;用於讀取一個識別符號的方法可以讀取到遇到操作符為止,然後將該操作符推回以進行重讀。

        b、字元流Reader及其子類

            字元流與位元組流的方法大體一致,唯一不同的就是,讀取的時候字元流是一個一個字元的讀或者讀字元陣列,而位元組流是一個一個位元組的讀或者讀位元組陣列,故關於字元流的類不再詳細闡述。

        c、兩個demo

            位元組流demo:

// 位元組流讀取檔案內容
		// stream.txt檔案內容是英文數字
		File file = new File("D:/work/test/stream.txt");
		FileInputStream is = new FileInputStream(file);
		int i;
		StringBuffer sb = new StringBuffer();
		// 一個位元組一個位元組的讀,如有位元組返回該位元組的int值,讀到檔案末尾返回-1
		// while ((i = is.read()) != -1) {
		// sb.append((byte) i);
		// }
		byte[] b = new byte[1024];
		// 將資料讀到快取陣列中,每次最多讀b.length個位元組,返回實際讀到的位元組數,如到檔案末尾返回-1
		while ((i = is.read(b)) != -1) {
			sb.append(new String(b, 0, i));
		}
		is.close();

            字元流demo:

// 字元流讀取檔案內容
		// 檔案包含中文
		File file = new File("D:/work/test/reader.txt");
		// 可指定編碼建立字元流物件
		InputStreamReader reader = new InputStreamReader(new FileInputStream(file), "GBK");
		StringBuffer sb = new StringBuffer();
		int i;
		// 一個字元一個字元的讀,如有字元返回該位元組的int值,讀到檔案末尾返回-1
		// while ((i = reader.read()) != -1) {
		// sb.append((char) i);
		// }
		char[] c = new char[1024];
		// 將資料讀到快取字元陣列中,每次最多讀b.length個位元組,返回實際讀到的字元數,如到檔案末尾返回-1
		while ((i = reader.read(c)) != -1) {
			sb.append(c, 0, i);
		}
		reader.close();

四、IO流設計模式之介面卡模式

        1、介面卡模式概念

        所謂介面卡模式就是將某個類的介面轉換為客戶端期望的另一個介面表示,其主要目的是相容性,讓原本不相干的兩個類可以協同一起工作。

        2、IO流中介面卡模式分析

         IO流中多處使用了介面卡模式,比較典型的就是位元組流轉字元流,目標介面字元流Reader與需要被適配的類InputStream原本是兩個不相干的類,為了滿足可以讀字元功能,有了介面卡者InputStreamReader。InputStreamReader的作用就是將位元組輸入流轉換為能讀取字元的字元輸入流。

         由InputStreamReader構造方法可知,接受一個位元組流InputStream,通過InputSream生成一個StreamDecoder物件,後續字元流讀寫都是靠這個StreamDecoder物件,間接利用了InputSream的功能。

public InputStreamReader(InputStream in) {
        super(in);
        try {
            sd = StreamDecoder.forInputStreamReader(in, this, (String)null); // ## check lock object
        } catch (UnsupportedEncodingException e) {
            // The default encoding should always be available
            throw new Error(e);
        }
    }

        五、IO流中設計模式之裝飾模式

            1、裝飾模式概念

                裝飾模式指的是在不必改變原類檔案和使用繼承的情況下,動態的擴充套件一個物件的功能。它是通過建立一個包裝物件,也就是裝飾來包裹真實物件。裝飾模式又叫包裝(Wrapper)模式。

            2、裝飾模式各角色

                (1)抽象構件(component)角色:給出一個抽象介面,以規範準備接收附加責任的物件。

                (2)具體構件(Concrete Component)角色:定義一個將要接收附加責任的類。

                (3)裝飾(Decorator)角色:持有一個構件(Component)物件的例項,並實現一個與抽象構件介面一致的介面。

                (4)具體裝飾(Concrete Decorator)角色:負責給構件物件新增上附加的責任。

                  注:責任可理解為功能,附加責任即為擴充套件的功能

            3、IO流中裝飾模式分析

                (1)抽象構件角色為InputStream抽象類,定義了位元組輸入流的基本方法。如read()方法

public abstract int read() throws IOException;

                (2)具體構件角色ByteArrayInputStream類,繼承了InputStream類,重寫了相關方法如read()方法。       

public synchronized int read() {
        return (pos < count) ? (buf[pos++] & 0xff) : -1;
    }

               (3) 裝飾角色FilterInputStream類,繼承了InputSream類,持有InputStream物件,重寫了相關方法如read()方法,將方法委派給具體構件角色(ByteArrayInputStream)執行。

//繼承抽象構件角色
class FilterInputStream extends InputStream {
 
    //持有抽象構件物件例項
	protected volatile InputStream in;

    protected FilterInputStream(InputStream in) {
        this.in = in;
    }

    public int read() throws IOException {
        //委派給具體構件角色執行
    	return in.read();
    }
}

        (4)具體裝飾角色BufferedInputStream類,繼承裝飾角色FilterInputSream類,添加了相關的屬性,重寫了read()方法,增強了原有read()方法功能,提高了讀效率。

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[];
 
    protected int count;

    protected int pos;

    protected int markpos = -1;

    protected int marklimit;
    
    public BufferedInputStream(InputStream in) {
        this(in, DEFAULT_BUFFER_SIZE);
    }

    public BufferedInputStream(InputStream in, int size) {
        super(in);
        if (size <= 0) {
            throw new IllegalArgumentException("Buffer size <= 0");
        }
        buf = new byte[size];
    }
    
    public synchronized int read() throws IOException {
        if (pos >= count) {
            fill();
            if (pos >= count)
                return -1;
        }
        return getBufIfOpen()[pos++] & 0xff;
    }
} 

        總結:介面卡模式是在介面卡中,重寫舊介面的方法來呼叫新介面方法,來實現舊介面不改變,同事使用新介面的目的,新介面適配舊介面。而裝飾模式,是裝飾器和舊介面實現相同的介面,在呼叫新介面的方法中,會呼叫舊介面的方法,並對其進行擴充套件。

        本章節主要以輸入流為主,未對輸出流詳細說明。如有錯誤,歡迎指正,謝謝!