從設計模式角度理解Java IO基本概念

來源:互聯網
上載者:User

標籤:擴充   都對   遠程服務   blog   比較   filter   它的   本質   for   

1、基本概念1.1、InputStream

最基本的位元組輸入資料流,抽象類別,定義了讀取原始位元組的所有基本方法
1.1.1、public abstract int read() throws IOException 讀取一個位元組的方法,最基礎的方法
1.1.2、public int read(byte b[], int off, int len) 讀取指定長度的位元組到位元組數組,基於方法1.1.1
1.1.3、public int read(byte b[]) throws IOException 讀取一個數組那麼多的位元組,基於 方法1.1.2
1.1.4、public long skip(long n) throws IOException 跳過一定位元組數,用到的比較少
1.1.5、public int available() throws IOException 返回可以讀取的最少位元組數,用到的比較少
1.1.6、mark(int readlimit)、void reset()和markSupported()這三個方法,並不是每個子類都支援,這裡設計得不合理,完全可以把這三個方法遷移到一個新的介面中去。
1.1.7、public void close() throws IOException 關閉輸入資料流

1.2、OutputStream

最基本的位元組輸出資料流,抽象類別,定義了寫入原始位元組的所有基本方法
1.2.1、public abstract void write(int b) throws IOException 寫入一個位元組,最基礎的方法
1.2.2、public void write(byte b[], int off, int len) throws IOException 將一個位元組數組中的部分位元組寫入,基於方法1.2.1
1.2.3、public void write(byte b[]) throws IOException 將一個位元組數組寫入,基於方法1.2.2
1.2.4、public void close() throws IOException 關閉輸出資料流
1.2.5、public void flush() throws IOException 重新整理輸出資料流

點評:InputStream和OutputStream定義了I/O領域最基礎的行為,也就是讀取和寫入一個位元組,同時使用了模板方法將讀取和寫入的行為進行了適當擴充。

2、擴充點一:對I/O流的繼承

有了抽象類別,就一定會有子類。針對不同的資料來源,InputStream和OutputStream存在三種子類:一種是基於記憶體的ByteArrayInputStream/ByteArrayOutputStream,一種是基於磁碟檔案的FileInputStream/FileOutputStream,還有一種是基於網路的SocketInputStream/SocketOutputStream。

2.1、FileInputStream/FileOutputStream

讀取寫入的源是作業系統的檔案
FileInputStream使用native方法進行底層檔案的讀取private native int read0() throws IOException;所有其他的read方法最終都是基於這個本地方法實現。
FileOutputStream使用native方法進行底層檔案的寫入private native void writeBytes(byte b[], int off, int len, boolean append)所有其他的write方法都是基於這個本地方法實現。

2.2、ByteArrayInputStream/ByteArrayOutputStream

讀取寫入的源是記憶體的一個數組,用的比較少。

2.3、SocketInputStream/SocketOutputStream

SocketInputStream使用 private native int socketRead0(FileDescriptor fd,byte b[], int off, int len,int timeout)這個native方法讀取遠程伺服器的資料流。所有read方法都是基於這個本地方法實現的。 SocketOutputStream 使用private native void socketWrite0(FileDescriptor fd, byte[] b, int off,int len)這個native方法來進行遠端資料流的寫入,所有的write方法都是基於這個方法實現的。

點評:InputStream和OutputStream是對流的抽象,不同的具體流通過繼承去實現,對於Java本地平台,最基本的就是基於檔案系統的流,當涉及到遠程系統,就會出現網路流,基於記憶體的流一般不會用到。

3、擴充點二:對IO流行為的擴充

裝飾模式可以對一個類的行為進行擴充,並且不改變它的介面,Java通過FilterInputStream/FilterOutputStream實現了裝飾模式。

責任鏈模式則是定義統一的介面,然後通過多個實現該介面的子類串列協作完成一項複雜的功能。Java通過將多個FilterInputStream/FilterOutputStream的子類串聯起來實現了責任鏈模式。

3.1、FilterInputStream/FilterOutputStream

FilterInputStream本身不實現輸入資料流的功能,而是通過建構函式傳入另一個InputStream的子類,把輸入資料流的功能交給它做。通過繼承FilterInputStream可以對輸入輸出資料流的行為進行擴充,這是裝飾模式的典型用法。通過多個裝飾類實現責任鏈模式,它將對一個輸入資料流的不同處理分散到不同的FilterInputStream中去。FilterOutputStream和FilterInputStream的原理一樣。

3.2、BufferedInputStream/BufferedOutputStream

繼承了FilterInputStream,實現了輸入資料流處理中的緩衝的功能。底層的流會先被讀取到一個位元組數組中,使用者使用BufferedInputStream讀取資料的時候,會先讀取位元組數組中的資料,讀完了才會調用底層的流進行進一步的讀取。這種方法可以提升讀取的效能。繼承了FilterOutputStream,實現了輸出資料流處理中的緩衝功能。當使用者寫入資料的時候,其實是先寫入到BufferedOutputStream的一個位元組數組中,當這個位元組數組滿了,才會真正調用底層的輸出資料流執行輸出動作。這種方法可以提升寫入的效能。在使用BufferedOutputStream的寫入功能時,一定要使用flush,因為緩衝數組不滿的時候是不會寫入底層流的,在寫入最後一點資料的時候,緩衝資料不一定被填滿,這時候就需要調用flush進行強制重新整理。

3.3、PrintStream

繼承FilterOutputStream,這個類的print和println方法可以把java的一些基本類型資料轉換成位元組寫入到底層輸出資料流,但是PrintStream對String的轉換是平台相關的,不同的平台會有不同的編碼,所以寫入到底層的位元組也不同,因此PrintStream只適合於測試輸出,不適合於一般的I/O操作,特別是網路流。

3.4、DataInputStream/DataOutputStream

這兩個類繼承了FilterInputStream/FilterOutputStream,用來實現將java基本類型轉換成二進位來進行讀寫操作,這兩個類的readUTF和writeUTF方法使用了一種特殊的UTF編解碼方式,只能用於java程式,因此不建議在網路流或者跨平台的應用中使用者兩個類

3.5、PushbackInputStream

繼承了FilterInputStream,提供了一種回退的機制,可以實現unread,本質是使用緩衝數組實現了,也就是說,回退的範圍是有限的。

4、Reader/Writer出現的原因

InputStream和OutputStream是面向位元組的,而人類的習慣是面向字元,因此InputStream和OutputStream對於程式猿的使用者體驗不是太好,於是就需要提供一些面向字元的流。由於DataInputStream/DataOutputStream在跨平台的情況下存在問題,因此,java設計者乾脆仿照InputStream和OutputStream重新設計了一套面向字元的I/O,也就是Reader/Writer

4.1、Reader

基本的字元輸入資料流,是個抽象類別
4.1.1、public abstract int read() throws IOException 讀取一個字元的方法,最基礎的方法
4.1.2、public int read(char b[], int off, int len) 讀取指定長度的字元到位元組數組,基於方法4.1.1
4.1.3、public int read(char b[]) throws IOException 讀取一個數組那麼多的字元,基於 方法4.1.2
4.1.4、public long skip(long n) throws IOException 跳過一定字元,用到的比較少
4.1.5、public int available() throws IOException 返回可以讀取的最少字元,用到的比較少
mark(int readlimit)、void reset()和markSupported()這三個方法,並不是每個子類都支援,這裡設計得不合理,完全可以把這三個方法遷移到一個新的介面中去。
4.1.6、public void close() throws IOException 關閉輸入資料流
4.1.7、public boolean ready() throws IOException 是否已經準備好

4.2、Writer

基本的字元輸出資料流,是個抽象類別
4.2.1、abstract public void write(char cbuf[], int off, int len) 抽象方法,用於寫入一個字元數組的一部分,需要子類實現
4.2.2、public void write(char cbuf[]) throws IOException 基於4.2.1、,寫入一個字元資料
4.2.3、public void write(int c) throws IOException 將一個int類型的堤16位作為一個字元寫入,基於4.2.1
4.2.4、public void write(String str) throws IOException 寫入一個字串,基於4.2.1
4.2.5、public void write(String str, int off, int len) throws IOException 寫入一個字串的一部分,基於4.2.1

5、字元與位元組之間的轉換5.1、InputStreamReader/OutputStreamWriter

由於電腦只識別位元組,所以Reader/Writer的資料來源最終還是位元組,而他們無法直接和位元組打交道,這時候就需要一個中介者將Reader/Writer和InputStream和OutputStream進行打通,於是就有了InputStreamReader和OutputStreamWriter

6、對Reader/Writer的繼承

不同源的Reader/Writer,他們都繼承InputStreamReader/OutputStreamWriter

6.1、FileReader/FileWriter

繼承了InputStreamReader/OutputStreamWriter,傳入FileInputStream/FileOutputStream作為底層的位元組I/O

6.2、CharArrayReader/CharArrayWriter

繼承了InputStreamReader/OutputStreamWriter,使用char數組作為資料來源,用的比較少

7、對Reader/Writer行為的擴充

類似於位元組流,也使用了裝飾模式和責任鏈模式

7.1、FilterReader/FilterWriter

對Reader/Writer的代理,底層使用其他Reader/Writer作為真正的操作。

7.2、BufferedReader/BufferedWriter

繼承了FilterReader/FilterWriter,BufferedReader使用char數組作為資料的緩衝區,讀取資料先從緩衝區讀,讀不到在從底層的Reader讀,Reader其實用到是更底層的InputStream,盡量不要用BufferedInputStream作為底層InputStream,兩層緩衝區沒有必要。BufferedWriter先寫入緩衝區,待緩衝區寫滿了再使用底層真正的Writer寫,Writer其實用的是更底層的OutputStream。盡量不要用BufferedOutputStream作為底層OutputStream,兩層緩衝區沒必要。

7.3、PushbackReader

繼承了FilterReader,實現了可退回的寫,本質是使用了一個char數組,所以可退回是有界限。

7.4、PrintWriter

用於取代PrintStream,它可以java基本類型轉換成位元組輸出,而且可以正確處理不同字元集的國際化問題。

至此,我們對java.io包下的相關類都做了詳細的解讀,接下來,讓我們看看第三方開源架構都對java IO進行了哪些擴充。

8、開源庫對Java IO的擴充

通過上面的解讀我們知道,java IO本身的擴充點有兩個,一個是通過繼承對資料來源的擴充,第二個是通過裝飾模式對行為進行擴充。下面介紹的commons-io選擇了對行為進行擴充,並提供一些IO操作的工具方法,簡化IO操作,而okio則不走尋常路,廢棄了java IO的體系,設計出了source/sink介面體系。

8.1、commons-io

8.1.1、擴充行為
最新的commons-io 2.5提供了對input和output的各種擴充,通過繼承FilterInputStream/FilterOutputStream實現
input:

  • AutoCloseInputStream:當IO流讀到EOF時,會進行自動關閉

  • BOMInputStream:用於處理含有BOM的輸入資料流,比如Windows下用記事本儲存的檔案

  • BoundedInputStream:含有讀取界限的輸入資料流,超過這個界限讀取將會停止

  • CountingInputStream:含有統計功能的輸入資料流

  • DemuxInputStream:這個輸入資料流會將真正的流儲存在ThreadLocal中

  • ProxyInputStream:一個抽象類別,提供了讀取一個位元組之前後之後的回調方法

  • TaggedInputStream:這個類在拋異常的時候會給異常設定標記,從而用於跟蹤異常

  • TeeInputStream:從一個源讀取資料,同時會儲存到一個指定的源,類似於unix的tee命令

  • UnixLineEndingInputStream:這個流在讀取分行符號的時候會按照unix格式讀取

  • WindowsLineEndingInputStream:這個流在讀取分行符號的時候會按照Windows格式讀取

output

  • ChunkedOutputStream:寫入流的時候按照chunk分批寫入

  • CountingOutputStream:具有統計功能的輸出資料流

  • DemuxOutputStream:這個輸出資料流會將真正的流儲存在ThreadLocal中

  • ProxyOutputStream:一個抽象類別,提供了寫入一個位元組之前後之後的回調方法

  • TaggedOutputStream:這個類在拋異常的時候會給異常設定標記,從而用於跟蹤異常

  • TeeOutputStream:寫資料到一個源,同時會儲存到一個指定的源,類似於unix的tee命令

8.1.2、工具方法IOUtils工具類,主要提供以下工具方法:

  • closeQuietly - 關閉一個流,忽略異常

  • toXxx/read - 從某個流讀取資料

  • write - 向某個流寫入資料

  • copy -從一個流複製到另一個流

  • contentEquals - 比較兩個流中的內容

8.2、okio

如果使用原生的Java IO進行基本類型的讀寫,我們需要使用DataInputStream/DataOutputStream以及BufferedReader/BufferedWriter這四個類,除此之外,我們還需要瞭解InputStreamReader/OutputStreamWriter以及Java IO之間的責任鏈,對於一般的Java開發人員來說,這顯然太複雜了。於是okio重新設計了介面Source/Sink,提供了訪問基本類型的介面和緩衝功能,同時屏蔽了底層複雜的IO體系,開發人員只要傳入InputStream和OutputStream就可以了。
具體的類別關係如下:

使用Okio的Java代碼如下:

try {

    BufferedSource bufferedSource = Okio.buffer(Okio.source(new FileInputStream("1.txt")));    int i = bufferedSource.readInt();    long l = bufferedSource.readLong();    String s = bufferedSource.readString(Charset.forName("UTF-8"));        BufferedSink bufferedSink = Okio.buffer(Okio.sink(new FileOutputStream("2.txt")));    bufferedSink.writeInt(1);    bufferedSink.writeLong(2L);    bufferedSink.writeString("123", Charset.forName("UTF-8"));} catch (Exception e) {    // process exception}

從設計模式角度理解Java IO基本概念

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.