IO之InputStream篇

來源:互聯網
上載者:User

Java I/O任務
一個Java的I/O任務,建立了一個串連兩個系統的資料轉送管道。它分為兩個部分:輸入資料流和輸出資料流。
輸入資料流,指的是通過流向本系統的記憶體傳輸資料的單向資料轉送通道。
輸出資料流,指的是通過流向外部系統傳輸資料的單向資料轉送通道。
輸入資料流
InputStream在 IO 中是表示位元組輸入資料流的所有類的超類, 抽象類別直接繼承於 Object 類,
其中的 read(byte[] b, int off, int len) 將輸入資料流中最多 len 個資料位元組讀入位元組數組。嘗試讀取多達 len 位元組,但可能讀取較少數量。以整數形式返回實際讀取的位元組數。
在輸入資料可用、檢測到流的末尾或者拋出異常前,此方法一直阻塞。

如果 b 為 null,則拋出 NullPointerException。

如果 off 為負,或 len 為負,或 off+len 大於數組 b 的長度,則拋出 IndexOutOfBoundsException。

部分轉自:http://blogger.org.cn/blog/more.asp?name=littcricket&id=23537

如果 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] 都不會受到影響。

如果不是因為流位於檔案末尾而無法讀取第一個位元組,則拋出 IOException。特別是,如果輸入資料流已關閉,則拋出 IOException。

類 InputStream 的 read(b, off, len) 方法只重複調用方法 read()。如果第一個這樣的調用導致 IOException,則從對 read(b, off, len) 方法的調用中返回該異常。如果對 read() 的任何後續調用導致 IOException,則該異常會被捕獲並將發生異常時的位置視為檔案的末尾;到達該點時讀取的位元組儲存在 b 中並返回傳生異常之前讀取的位元組數。建議讓子類提供此方法的更有效實現。

參數:
b - 讀入資料的緩衝區。
off - 在其處寫入資料的數組 b 的初始位移量。
len - 要讀取的最大位元組數。
返回:
讀入緩衝區的總位元組數,如果由於已到達流末尾而不再有資料,則返回 -1。
拋出:
IOException - 如果發生 I/O 錯誤。
NullPointerException - 如果 b 為 null。

解釋一下什麼是阻塞式IO:

就是在進行讀寫的時候調用了某個方法,如 read() 或 writer() 方法,
在該方法執行完之前,線程會一直等待,直到該方法執行完畢。

例如你用鍵盤讀入資料:

Java code

    InputStream is = new InputStream(System.in); BufferReadered buffer = new BufferReader(new InputStreamReader(is));//它得等你鍵盤輸入完畢,它才能讀(在這之前它是阻塞的)

java.nio 包或許有非阻塞方法, 需要用到非阻塞方法的時候可以瞭解。

read方法解讀
這些方法的核心。實際上就是read()方法。
這個方法的JavaDoc中指出:
“如果因已到達流末尾而沒有可用的位元組,則傳回值 -1。如果因已到達流末尾而沒有可用的位元組,則傳回值 -1。在輸入資料可用、檢測到流的末尾或者拋出異常前,此方法一直阻塞。”

1,read方法會在能夠返迴流中的位元組之前,一直阻塞線程。這就是說,read方法是一個低消耗的監聽和讀取I/O傳輸的好方法。
這個方法的實現,具有非常高的效能。

2,如果輸入資料流的I/O系統在執行這個read方法時拋出異常,那麼顯然這個方法會非正常結束,這個也是毫無疑問的。

3,“如果因已到達流末尾而沒有可用的位元組,則傳回值 -1。”
這句話看似沒有問題,但實際上有非常大的歧義!什麼是流的末尾?只有流的末尾才能返回-1嗎?

InputStream類和SocketInputStream類的源碼解讀
通過查看InputStream類的源碼,我發現實際上,流,就好比是雙向2車道的高速公路。它傳輸資料是一批一批的。我把它叫做“批資料”。
假設A=======B兩個系統通過一個I/O流來串連。
那麼,從B端通向A端的車道,就叫作A的“輸入資料流”。同一條車道,在B這邊,叫作B的“輸出資料流”。
同理,從A端通向B端的車道,就叫作A的“輸出資料流”。同一條車道,在B這邊,就叫作B的“輸入資料流”。

資料在這條高速公路上,不是一條一條跑的,而是一批一批跑。

OutputStream類,此抽象類別是表示輸出位元組流的所有類的超類。輸出資料流接受輸出位元組並將這些位元組發送到某個接收器。

OutputStream類的write方法,每執行一次,就向這條高速公路上發送了一批資料。OutputStream類的一些子類,它們並不是在每次write()方法執行之後立刻把這批資料發送到資料高速公路上的。而是只有在執行flush()方法之後,才把之前write的多批資料真正地發送到資料通道中。

這樣,多個write()方法發送的資料就變為了一批資料了!

通過read()方法讀入時,當讀完該批資料之後,如果再一次執行read()方法,就會立刻返回-1。

實際上,這是並沒有到達流的末尾!僅僅是讀完了一批發送的資料而已!

如果我們又一次執行read()方法,那麼,如果:

1,流沒有結束。也就是說,對面的發送端可能還會發送下一批資料時,就會進入阻塞狀態。當前線程暫停,直到讀取到輸入資料流中下一批資料的第一個位元組。

2,流結束了。也就是說,對面的發送端不再發送任何資料,也即:這條資料通道已經沒有用了,這時,可以說“到達流的末尾”了!返回-1。

   

所以,InputStream及其子類的read()方法的注釋是不完整的!

Read()方法的注釋,應該這麼說:
read

public abstract int read()

                  throws IOException

從輸入資料流讀取下一個資料位元組。返回 0 到 255 範圍內的 int 位元組值。
如果在讀完一批資料後首次調用read()方法,那麼返回-1。表示這批資料已經讀完了!
如果因已到達流末尾而沒有可用的位元組,則傳回值 -1。在輸入資料可用、檢測到流的末尾或者拋出異常前,此方法一直阻塞。
子類必須提供此方法的一個實現。
返回:
下一個資料位元組;
如果剛讀完一批資料,則返回-1;
如果到達流的末尾,則返回 -1。
拋出:
IOException - 如果發生 I/O 錯誤。

如何正確使用Java I/O輸出和讀入資料
明白了Java的I/O流的工作機理和read方法的執行結果,我們就能夠正確地使用Java I/O系統輸出和讀入資料了。

如何分批輸出資料
由於read(…)方法是分批讀取資料的,所以,我們應該在輸出端正確地分批輸出資料。
Write(…)方法,然後執行flush()方法能夠將多批資料合併成一批資料輸出。
儘管OutputStream這個基類的flush()方法是無用的,但是由於我們得到的OutputStream類型的輸出對象都是這個類的子類的對象,所以,我們還是應該盡量使用flush()方法強制向輸出資料流中物理輸出資料,以避免錯誤。

如何分批讀取資料

    我們常常使用public int read(byte[] b,

                int off,
                int len)
         throws IOException
這個方法來讀取一批資料。
查看這個方法的原始碼,我們可以發現,它在讀取完一批資料時,又執行了一次read()方法,由於前面論述的原因,這個方法立刻返回-1,然後這個方法退出,返回這次讀取的位元組數。
因此,如果我們要讀取一批資料,可以採用如下幾種方法:
使用read()判斷-1來讀完一批資料:
    程式碼範例:
Int byte;
While((byte=inputStream.read())!=-1 ){
    Byte就是返回的位元組。

}
如果讀完一批資料後再一次執行read方法,將會立刻返回-1,表示這批資料已經讀完。

使用read(byte[] buffer)判斷是否返回小於buffer.length或者-1來判斷是否讀完一批資料
上面那樣一個一個位元組讀取資料比較麻煩,我們通常使用一個位元組數組來讀取資料。
read(byte[] buffer)方法返回:
1,buffer.length,表示從輸入資料流中讀取到的資料塞滿了這個位元組數組。此時,可能已經讀完了這批資料,也可能沒有讀完這批資料。
    如果剛好讀完,那麼再執行一次read()方法,就會返回-1,表示這批資料已經讀完,而不是表示流已經結束。
2,小於buffer.length。表示讀完了該批資料。並且執行了一次read()方法,返回-1,表示這批資料已經讀完。
3,-1。這批資料已經讀完了。 不可能是表示流已經結束。因為之前就會退出while迴圈。
  
程式碼範例:
Byte[] buffer=new byte[1024];
int size=buffer.length;
While(size!=-1 || size>=buffer.length){
Size=inputStream.read(buffer));
將資料讀到數組buffer中。
如果讀到了-1,或者 讀出的資料少於buffer的尺寸,表示已經讀完該批資料,不再迴圈讀取資料了!
}

讀入一批資料的操作必須對應輸出一批資料的操作
讀入一批資料的操作必須對應輸出一批資料的操作。否則,讀入資料的線程會一直阻塞,等待輸出端輸出下一批資料。
如果對方也需要我們提供輸出資料,那麼就可能會使整個流的兩端的線程互相等待,死結住。並且這個I/O流也會永遠不釋放,這樣就會使系統的資源耗盡。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.