java中的輸入輸出資料流

來源:互聯網
上載者:User
  1. 轉自  http://blog.csdn.net/aflylove/article/details/1338549
  2.      網路程式的很大一部分是簡單的輸入輸出,即從一個系統向另一個系統移動位元組。位元組就是位元組,在很大程度上,讀伺服器發送的資料與讀取檔案沒什麼不同;向客戶傳送資料與寫入一個檔案也沒有什麼區別。

       Java中輸入和輸出組織不同於大多數其他語言。它是建立在流(stream)上。不同的基本流類(如java.io.FileInputStream和sun.net.TelnetOutputStream)用於讀寫特定的資料資源。但是所有的基本輸出資料流使用同一種基本方法讀資料。

       過濾器流可以串連到輸入資料流或輸出資料流。它可以修改已經讀出或寫人的資料(例如,加密或壓縮資料),或者可以簡單地提供附加方法將已經讀出或寫入的資料轉化成其他格式。

       最後Reader和Writer也可以連結到輸入資料流和輸出資料流,從而允許程式讀出和寫入文本(即字元)而不是位元組。如果使用正確,Reader和Writer能夠處理多種類型的字元編碼,包括SJIS和UTF-8等多位元組字元集。

 

一、輸出資料流

java的基本輸出資料流是 java.io.OutputStream.

public abstract class OutputStream

 

n         public abstract void write(int b) throws IOException

n         public void write(byte[] data) throws IOException

n         public void write(byte[] data,int offset,int length) throws IOException

n         public void flush() throws IOException

n         public void close() throws IOException

OutputStream的子類使用這些方法向指定媒體寫入資料。

 

我始終相信,我們理解了為什麼它們存在,就會更好地記住它們,好,現在開始說一下OutputStream類的方法的由來

Ø         public abstract void write(int b) throws IOException

 

OutputStream的基本方法是write(int b)。該方法將介於0到255之間的整數看作變數,並將相應的位元組寫到一個輸出資料流。該方法聲明是個抽象方法,因為子類需要改變它以處理特定媒體。例如,ByteArrayOutputStream可以使用拷貝位元組到其數組的純Java代碼來實現方法。但是,FileOutputStream就需要使用代碼,此代碼應該理解如何在主機平台上將資料寫入檔案。注意:儘管該方法把整形值作為變數,但是它實際上寫入的是一個無符號位元組。Java沒有無符號位元組資料類型,因此這裡使用整型來代替。無符號位元組和有符號位元組之間的真正區別是編譯器對它們的解釋。二者都是由8位組成,並且當使用write(int
b)將一個int寫入到網路連接流時,只有8位元據傳送。如果將一個超出0-255範圍的int傳給write(int b),則寫入該數位低位位元組,而忽略餘下的三個位元組(大家都知道java的int是4個位元組的,這裡本質就是將int轉換為byte)。

 

Ø         public void write(byte[] data) throws IOException和public void write(byte[]
data,int offset,int length) throws IOException

 

每次寫入一個位元組通常效率不高。因此,大部分TCP/IP程式將資料存入一定長度的緩衝區,即在記憶體中累積位元組,並僅當累積了一定數目位元組或過了一定的時間段,才將它們發送到最終的目的地。因此write(byte[] data)和write(byte[] data,int offset,int length)就是這樣產生了。

 

Ø         public void flush() throws IOException

 

我們可以在軟體中或直接在Java代碼中對流實施緩衝操作,也可以在網路硬體中對流實施緩衝操作。就好像BufferedOutputStream或BufferedWriter連結到底層流來實現流緩衝。因此,如果正在寫入資料,則重新整理輸出資料流是相當重要的。例如,假設已經寫入了一個300位元組的請求給一個HTTP Keep-Alive的HTTP伺服器,通常希望在發送更多資料之間等待響應。但是,如果輸出資料流有一個1024位元組的緩衝區,則該流可能在將資料發送出緩衝區之前正在等待更多的資料到達,但是這些資料似乎不會到達的,因為它們還沒有發送出去,但是緩衝流不會發送資料給伺服器,除非它從底層流獲得更多的資料,但是底層流不會發送更多的資料,除非它從伺服器獲得資料,而伺服器不會發送資料,除非它獲得保留在緩衝區中的資料(死結了!),flush()方法就可以解決了這個僵局,因為即使緩衝區未滿,他也會強制要求實行緩衝操作的流傳送資料。注意:是否對流實行了緩衝操作,這決定於你如何獲得指向流的引用(例如,不論是否希望對System.out執行緩衝操作,都會對其實施緩衝)。如果重新整理流需要重新整理時,就必須重新整理,但是如果重新整理失敗了就會導致不可預料、不可重複的程式掛起(flush()傳回值是void啊),如果事先不瞭解掛起問題所在,就很難解決這個問題了。因此,在關閉所有流之前,應當立即重新整理它們。否則,關閉流前,緩衝區中的剩餘資料可能會丟失。

 

Ø         public void close() throws IOException

 

最後當利用完流之後,應當調用close()方法關閉流。它會釋放所有與這個流相關的資源,如檔案控制代碼或連接埠。一旦輸出資料流關閉了,再向其寫入資料就會觸發IOException異常。但是,有些類型可能允許對對象進行一定操作。如一個已關閉的ByteArrayOutputStream仍然可以轉化成一個實際的位元組數組,而且一個已關閉的DigestOutputStream仍可以返回其摘要。

 

二、輸入資料流

java的基本輸入資料流是java.io.InputStream

public abstract class InputStream

n         public abstract int read() throws IOException

n         public int read(byte[] data) throws IOException

n         public int read(byte[] data,int offset,int length) throws IOException

n         public long skip(long n) throws IOException

n         public int available() throws IOException

n         public void close() throws IOException

InputStream的具體子類使用這些方法從指定媒體讀取資料。但是不論讀取何種資源,幾乎只能使用這六種方法。有時你甚至可能不知道正在從哪種類型的流中讀取資料。如隱藏在sun.net包中TelnetInputStream是一個文檔沒有說明的類。TelnetInputStream的執行個體由java.net包中的多種方法返回;如java.net.URL的openStram()方法。但是,這些方法僅聲明了返回InputStream,而不是更加明確的子類TelnetInputStream,這又是多態性在起作用了。子類的執行個體可以作為超類的執行個體透明使用。

來了,又來說明方法的由來了。

Ø         public abstract void read() throws IOException

 

InputStream類的基本方法是沒有參量的read()方法(這個與OutputStream不同了)。該方法從輸入資料流資源讀取一個單個位元組資料並將資料作為0到255之間的數返回,返回-1時表示流的結尾。因為Java沒有無符號位元組的資料類型,所以資料以整數型別返回。Read()方法等待和阻塞該方法後人和代碼的執行,直到獲得資料的一個位元組並準備讀取該位元組。因此,輸入和輸出可能相當慢,這時使用者如果需要完成其他比較重要的任務時,最好試圖將I/O放到它們自己的線程中。Read()方法被聲明為抽象方法,因為子類需要改變它來處理特定媒體。給個例子

byte[] input=new byte[10];

for(int i=0;i

       int b=in.read();

       if(b==-1) break;

       input[i]=(byte)b;

}

上面儘管read()方法僅讀取位元組,但是它返回的是整型值。因此在將結果儲存到位元組數組之前,需要一個類型轉換的過程。當然,這會產生一個介於-128到127的有符號位元組,而不是read()方法返回的0到255之間的一個無符號位元組。但是,只要使用者清楚使用的是無符號還是有符號位元組就不會有很大問題。因此,我們可以把一個有符號位元組轉化成無符號位元組(轉換的原因是只有範圍在0-255的整數才可以被儲存在java的一個byte類型的變數中)。

int i=b>=0?b:256+b;

這裡費了大篇幅,說明了read()返回的與java的byte類型的處理問題,大家可要注意阿,如果對java的未經處理資料類型還有興趣,可以看一下我的未經處理資料類型學習筆記(未完成)。

 

 

Ø         public int read(byte[] data) throws IOException、public int read(byte[]
data,int offset,int length) throws IOException

 

每次讀取一個位元組和每次寫入一個位元組效率都不高,因此read(byte[] data)和read(byte[] data,int offset,int length)也相應產生了。這兩個方法將從流中讀取的多個位元組填充到一個指定的數組中。注意:這些填充到數組的操作不一定會成功的。一個很普遍的情況是一個讀試圖不會完全失敗也不會完全成功,它可能讀出請求資料的一部分位元組,而不是全部位元組。例如,當實際上只有512位元組已經到達伺服器時,使用者可能會試圖從一個網路流上讀取1024位元組,而其他位元組仍然在傳送中,這些位元組最終會到達伺服器,但到達時卻已是不可以獲得的。因此,多位元組讀取方法會返回實際讀取的位元組數目。給個例子

byte[] input=new byte[1024];

int bytesRead=in.read(input);

程式碼片段試圖從InputStream in讀取1024位元組到數組input中。但是,如果僅有512位元組可以獲得,則這些位元組就是將要讀取的全部位元組,並且bytesRead值會設為512。但我們為了保證在實際上讀取到所有的位元組,怎麼辦?看

int bytesRead=0;

int byteToRead=1024;

byte[] input=new byte[byteToRead];

while(bytesRead

       bytesRead+=in.read(input,bytesRead,byteToRead-bytesRead);

}

Ø         public int available() throws IOException

 

如果由於某種原因使用者不希望讀取資料,除非使用者想要的全部資料可以立即得到,這時候就可以用available()方法返回的位元組數是能夠讀取的最小位元組數,而在實際上可以讀取更多的位元組,但是能夠讀取的位元組資料至少與available()返回的位元組數一樣多。

看例子

int bytesAvailable=in.available();

byte[] input=new byte[bytesAvailable];

int byteTead=in.read(input,0,bytesAvailable);

//其他代碼

這裡我們可以斷言bytesRead正好等於bytesAvailable,但不能斷言bytesRead>0,因為available()返回0是有可能的。

流結束時:

available()返回0;

read(byte[] data,int offset,int length)通常返回-1;

       流沒有結束,可讀取位元組數即available()得到的值為0時

read(byte[] data,int offset,int length)會忽略流的結束,返回0;

 

Ø         public long skip(long n) throws IOException

 

在極少數情況下,使用者可能希望跳過資料而不去讀取它們。Skip()方法就是實現這個功能的。這個方法在從檔案讀取資料時較為有用,而在網路連接流上則用處較小。因為網路連接流是有序的而且通常很慢,因此讀取資料的耗時不會太多的超過跳過資料的耗時。檔案可以隨機訪問,因此我們通過重定位檔案指標就能簡單的實現資料的跳轉,而不是跳過每一個位元組。

 

Ø         public void close() throws IOException

 

和輸出資料流一樣,程式利用完輸入資料流之後,就應該調用close()方法關閉該輸入資料流了(要記住啊)。該方法會釋放與輸入資料流有關的所有資源,如檔案控制代碼和連接埠。一旦輸入資料流關閉,再從它讀取資料時會觸發IOException。但是,有些類型的流可能仍允許對對象進行一定的操作。例如,使用者通常不希望從java.security.DigestInputStream中擷取報文摘要,除非已經讀取了所有資料並且關閉了輸入資料流。

 

看到這裡或許你還會問怎麼還有三個方法沒有呢,對,還有三個不常用的方法

public void mark(int readAheadLimit)

public void reset() throws IOException

public boolean markSupported();

這些方法允許程式備份和重新讀取已經讀取過的資料。要實現這個功能,需要用mark()方法在輸入資料流中的當前位置作個標記,在以後的某點可以使用reset()方法重新將流定位到標記處,隨後的讀取將返回從標記初開始的資料。但是,從標記處到重新將流錨點不能任意長。重新置放到標記處之前允許讀取的位元組數就是由mark()的變數readAheadLimit決定。多長就會觸發IOException,而且任何指定時刻,輸入資料流中只可以有一個標記,如果標記了第二個標記,就會覆蓋第一個標記了。其實標記和重新設定位置都是通過儲存從內部緩衝區中的標記位置讀出每一位元組來實現。最麻煩的狀況是,並非所有輸入資料流都支援標記和重新設定位置的。所以在設定之前要用markSupported()方法檢測一下。

實際上不支援標記和重新設定位置的流多於支援它們的流。Elliotte Rusty Harold大師覺得這幾個方法設計的標準不高,將功能性與一個許多甚至可能是大部分子類都停用抽象超類結合是一個相當拙劣的想法。最好是將這三個方法放在不同的介面中。提供類似於markSupported()方法在運行時進行功能性檢測是較為傳統,非物件導向的解決方案。物件導向的方法將通過介面和類把該方法嵌入到物件導向系統中,從而在編譯時間檢測所有的流。

Java.io中總是支援標記的輸入資料流:BufferedInputStream和ByteArrayInputStream。

相關文章

聯繫我們

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