網(wǎng)絡(luò)程序的很大一部分是簡單的輸入輸出,即從一個(gè)系統(tǒng)向另一個(gè)系統(tǒng)移動(dòng)字節(jié)。字節(jié)就是字節(jié),在很大程度上,讀服務(wù)器發(fā)送的數(shù)據(jù)與讀取文件沒什么不同;向客戶傳送數(shù)據(jù)與寫入一個(gè)文件也沒有什么區(qū)別。
java中輸入和輸出組織不同于大多數(shù)其他語言。它是建立在流(stream)上。不同的基本流類(如java.io.FileInputStream和sun.net.TelnetOutputStream)用于讀寫特定的數(shù)據(jù)資源。但是所有的基本輸出流使用同一種基本方法讀數(shù)據(jù)。
過濾器流可以連接到輸入流或輸出流。它可以修改已經(jīng)讀出或?qū)懭说臄?shù)據(jù)(例如,加密或壓縮數(shù)據(jù)),或者可以簡單地提供附加方法將已經(jīng)讀出或?qū)懭氲臄?shù)據(jù)轉(zhuǎn)化成其他格式。
最后Reader和Writer也可以鏈接到輸入流和輸出流,從而答應(yīng)程序讀出和寫入文本(即字符)而不是字節(jié)。假如使用正確,Reader和Writer能夠處理多種類型的字符編碼,包括SJIS和UTF-8等多字節(jié)字符集。
一、輸出流
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的子類使用這些方法向指定媒體寫入數(shù)據(jù)。
我使用相信,我們理解了問什么它們存在,就會(huì)更好地記住它們,好,現(xiàn)在開始說一下OutputStream類的方法的由來
Ø public abstract void write(int b) throws IOException
OutputStream的基本方法是write(int b)。該方法將介于0到255之間的整數(shù)看作變量,并將相應(yīng)的字節(jié)寫到一個(gè)輸出流。該方法聲明是個(gè)抽象方法,因?yàn)樽宇愋枰淖兯蕴幚硖囟襟w。例如,ByteArrayOutputStream可以使用拷貝字節(jié)到其數(shù)組的純Java代碼來實(shí)現(xiàn)方法。但是,F(xiàn)ileOutputStream就需要使用代碼,此代碼應(yīng)該理解如何在主機(jī)平臺(tái)上將數(shù)據(jù)寫入文件。注重:盡管該方法把整形值作為變量,但是它實(shí)際上寫入的是一個(gè)無符號(hào)字節(jié)。Java沒有無符號(hào)字節(jié)數(shù)據(jù)類型,因此這里使用整型來代替。無符號(hào)字節(jié)和有符號(hào)字節(jié)之間的真正區(qū)別是編譯器對它們的解釋。二者都是由8位組成,并且當(dāng)使用write(int b)將一個(gè)int寫入到網(wǎng)絡(luò)連接流時(shí),只有8位數(shù)據(jù)傳送。假如將一個(gè)超出0-255范圍的int傳給write(int b),則寫入該數(shù)字的低位字節(jié),而忽略余下的三個(gè)字節(jié)(大家都知道java的int是4個(gè)字節(jié)的,這里本質(zhì)就是將int轉(zhuǎn)換為byte)。
Ø public void write(byte[] data) throws IOException和public void write(byte[] data,int offset,int length) throws IOException
每次寫入一個(gè)字節(jié)通常效率不高。因此,大部分TCP/ip程序?qū)?shù)據(jù)存入一定長度的緩沖區(qū),即在內(nèi)存中累積字節(jié),并僅當(dāng)累積了一定數(shù)目字節(jié)或過了一定的時(shí)間段,才將它們發(fā)送到最終的目的地。因此write(byte[] data)和write(byte[] data,int offset,int length)就是這樣產(chǎn)生了。
Ø public void flush() throws IOException
我們可以在軟件中或直接在Java代碼中對流實(shí)施緩沖操作,也可以在網(wǎng)絡(luò)硬件中對流實(shí)施緩沖操作。就似乎BufferedOutputStream或BufferedWriter鏈接到底層流來實(shí)現(xiàn)流緩沖。因此,假如正在寫入數(shù)據(jù),則刷新輸出流是相當(dāng)重要的。例如,假設(shè)已經(jīng)寫入了一個(gè)300字節(jié)的請求給一個(gè)HTTP Keep-Alive的HTTP服務(wù)器,通常希望在發(fā)送更多數(shù)據(jù)之間等待響應(yīng)。但是,假如輸出流有一個(gè)1024字節(jié)的緩沖區(qū),則該流可能在將數(shù)據(jù)發(fā)送出緩沖區(qū)之前正在等待更多的數(shù)據(jù)到達(dá),但是這些數(shù)據(jù)似乎不會(huì)到達(dá)的,因?yàn)樗鼈冞€沒有發(fā)送出去,但是緩沖流不會(huì)發(fā)送數(shù)據(jù)給服務(wù)器,除非它從底層流獲得更多的數(shù)據(jù),但是底層流不會(huì)發(fā)送更多的數(shù)據(jù),除非它從服務(wù)器獲得數(shù)據(jù),而服務(wù)器不會(huì)發(fā)送數(shù)據(jù),除非它獲得保留在緩沖區(qū)中的數(shù)據(jù)(死鎖了!),flush()方法就可以解決了這個(gè)僵局,因?yàn)榧词咕彌_區(qū)未滿,他也會(huì)強(qiáng)制要求實(shí)行緩沖操作的流傳送數(shù)據(jù)。注重:是否對流實(shí)行了緩沖操作,這決定于你如何獲得指向流的引用(例如,不論是否希望對System.out執(zhí)行緩沖操作,都會(huì)對其實(shí)施緩沖)。假如刷新流需要刷新時(shí),就必須刷新,但是假如刷新失敗了就會(huì)導(dǎo)致不可預(yù)料、不可重復(fù)的程序掛起(flush()返回值是void啊),假如事先不了解掛起問題所在,就很難解決這個(gè)問題了。因此,在關(guān)閉所有流之前,應(yīng)當(dāng)立即刷新它們。否則,關(guān)閉流前,緩沖區(qū)中的剩余數(shù)據(jù)可能會(huì)丟失。
Ø public void close() throws IOException
最后當(dāng)利用完流之后,應(yīng)當(dāng)調(diào)用close()方法關(guān)閉流。它會(huì)釋放所有與這個(gè)流相關(guān)的資源,如文件句柄或端口。一旦輸出流關(guān)閉了,再向其寫入數(shù)據(jù)就會(huì)觸發(fā)IOException異常。但是,有些類型可能答應(yīng)對對象進(jìn)行一定操作。如一個(gè)已關(guān)閉的ByteArrayOutputStream仍然可以轉(zhuǎn)化成一個(gè)實(shí)際的字節(jié)數(shù)組,而且一個(gè)已關(guān)閉的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的具體子類使用這些方法從指定媒體讀取數(shù)據(jù)。但是不論讀取何種資源,幾乎只能使用這六種方法。有時(shí)你甚至可能不知道正在從哪種類型的流中讀取數(shù)據(jù)。如隱藏在sun.net包中TelnetInputStream是一個(gè)文檔沒有說明的類。TelnetInputStream的實(shí)例由java.net包中的多種方法返回;如java.net.URL的openStram()方法。但是,這些方法僅聲明了返回InputStream,而不是更加明確的子類TelnetInputStream,這又是多態(tài)性在起作用了。子類的實(shí)例可以作為超類的實(shí)例透明使用。
來了,又來說明方法的由來了。
Ø public abstract void read() throws IOException
InputStream類的基本方法是沒有參量的read()方法(這個(gè)與OutputStream不同了)。該方法從輸入流資源讀取一個(gè)單個(gè)字節(jié)數(shù)據(jù)并將數(shù)據(jù)作為0到255之間的數(shù)返回,返回-1時(shí)表示流的結(jié)尾。因?yàn)镴ava沒有無符號(hào)字節(jié)的數(shù)據(jù)類型,所以數(shù)據(jù)以整型類型返回。Read()方法等待和阻塞該方法后人和代碼的執(zhí)行,直到獲得數(shù)據(jù)的一個(gè)字節(jié)并預(yù)備讀取該字節(jié)。因此,輸入和輸出可能相當(dāng)慢,這時(shí)用戶假如需要完成其他比較重要的任務(wù)時(shí),最好試圖將I/O放到它們自己的線程中。Read()方法被聲明為抽象方法,因?yàn)樽宇愋枰淖兯鼇硖幚硖囟襟w。給個(gè)例子
byte[] input=new byte[10];
for(int i=0;i<input.length;i++){
int b=in.read();
if(b==-1) break;
input[i]=(byte)b;
}
上面盡管read()方法僅讀取字節(jié),但是它返回的是整型值。因此在將結(jié)果存儲(chǔ)到字節(jié)數(shù)組之前,需要一個(gè)類型轉(zhuǎn)換的過程。當(dāng)然,這會(huì)產(chǎn)生一個(gè)介于-128到127的有符號(hào)字節(jié),而不是read()方法返回的0到255之間的一個(gè)無符號(hào)字節(jié)。但是,只要用戶清楚使用的是無符號(hào)還是有符號(hào)字節(jié)就不會(huì)有很大問題。因此,我們可以把一個(gè)有符號(hào)字節(jié)轉(zhuǎn)化成無符號(hào)字節(jié)(轉(zhuǎn)換的原因是只有范圍在0-255的整數(shù)才可以被存儲(chǔ)在java的一個(gè)byte類型的變量中)。
int i=b>=0?b:256+b;
這里費(fèi)了大篇幅,說明了read()返回的與java的byte類型的處理問題,大家可要注重阿,假如對java的原始數(shù)據(jù)類型還有愛好,可以看一下我的原始數(shù)據(jù)類型學(xué)習(xí)筆記(未完成)。
Ø public int read(byte[] data) throws IOException、public int read(byte[] data,int offset,int length) throws IOException
每次讀取一個(gè)字節(jié)和每次寫入一個(gè)字節(jié)效率都不高,因此read(byte[] data)和read(byte[] data,int offset,int length)也相應(yīng)產(chǎn)生了。這兩個(gè)方法將從流中讀取的多個(gè)字節(jié)填充到一個(gè)指定的數(shù)組中。注重:這些填充到數(shù)組的操作不一定會(huì)成功的。一個(gè)很普遍的情況是一個(gè)讀試圖不會(huì)完全失敗也不會(huì)完全成功,它可能讀出請求數(shù)據(jù)的一部分字節(jié),而不是全部字節(jié)。例如,當(dāng)實(shí)際上只有512字節(jié)已經(jīng)到達(dá)服務(wù)器時(shí),用戶可能會(huì)試圖從一個(gè)網(wǎng)絡(luò)流上讀取1024字節(jié),而其他字節(jié)仍然在傳送中,這些字節(jié)最終會(huì)到達(dá)服務(wù)器,但到達(dá)時(shí)卻已是不可以獲得的。因此,多字節(jié)讀取方法會(huì)返回實(shí)際讀取的字節(jié)數(shù)目。給個(gè)例子
byte[] input=new byte[1024];
int bytesRead=in.read(input);
代碼段試圖從InputStream in讀取1024字節(jié)到數(shù)組input中。但是,假如僅有512字節(jié)可以獲得,則這些字節(jié)就是將要讀取的全部字節(jié),并且bytesRead值會(huì)設(shè)為512。但我們?yōu)榱吮WC在實(shí)際上讀取到所有的字節(jié),怎么辦?看
int bytesRead=0;
int byteToRead=1024;
byte[] input=new byte[byteToRead];
while(bytesRead<byteToRead){
bytesRead+=in.read(input,bytesRead,byteToRead-bytesRead);
}
Ø public int available() throws IOException
假如由于某種原因用戶不希望讀取數(shù)據(jù),除非用戶想要的全部數(shù)據(jù)可以立即得到,這時(shí)候就可以用available()方法返回的字節(jié)數(shù)是能夠讀取的最小字節(jié)數(shù),而在實(shí)際上可以讀取更多的字節(jié),但是能夠讀取的字節(jié)數(shù)據(jù)至少與available()返回的字節(jié)數(shù)一樣多。
看例子
int bytesAvailable=in.available();
byte[] input=new byte[bytesAvailable];
int byteTead=in.read(input,0,bytesAvailable);
//其他代碼
這里我們可以斷言bytesRead正好等于bytesAvailable,但不能斷言bytesRead>0,因?yàn)閍vailable()返回0是有可能的。
流結(jié)束時(shí):
available()返回0;
read(byte[] data,int offset,int length)通常返回-1;
流沒有結(jié)束,可讀取字節(jié)數(shù)即available()得到的值為0時(shí)
read(byte[] data,int offset,int length)會(huì)忽略流的結(jié)束,返回0;
Ø public long skip(long n) throws IOException
在極少數(shù)情況下,用戶可能希望跳過數(shù)據(jù)而不去讀取它們。Skip()方法就是實(shí)現(xiàn)這個(gè)功能的。這個(gè)方法在從文件讀取數(shù)據(jù)時(shí)較為有用,而在網(wǎng)絡(luò)連接流上則用處較小。因?yàn)榫W(wǎng)絡(luò)連接流是有序的而且通常很慢,因此讀取數(shù)據(jù)的耗時(shí)不會(huì)太多的超過跳過數(shù)據(jù)的耗時(shí)。文件可以隨機(jī)訪問,因此我們通過重定位文件指針就能簡單的實(shí)現(xiàn)數(shù)據(jù)的跳轉(zhuǎn),而不是跳過每一個(gè)字節(jié)。
Ø public void close() throws IOException
和輸出流一樣,程序利用完輸入流之后,就應(yīng)該調(diào)用close()方法關(guān)閉該輸入流了(要記住啊)。該方法會(huì)釋放與輸入流有關(guān)的所有資源,如文件句柄和端口。一旦輸入流關(guān)閉,再從它讀取數(shù)據(jù)時(shí)會(huì)觸發(fā)IOException。但是,有些類型的流可能仍答應(yīng)對對象進(jìn)行一定的操作。例如,用戶通常不希望從java.security.DigestInputStream中獲取報(bào)文摘要,除非已經(jīng)讀取了所有數(shù)據(jù)并且關(guān)閉了輸入流。
看到這里或許你還會(huì)問怎么還有三個(gè)方法沒有呢,對,還有三個(gè)不常用的方法
public void mark(int readAheadLimit)
public void reset() throws IOException
public boolean markSupported();
這些方法答應(yīng)程序備份和重新讀取已經(jīng)讀取過的數(shù)據(jù)。要實(shí)現(xiàn)這個(gè)功能,需要用mark()方法在輸入流中的當(dāng)前位置作個(gè)標(biāo)記,在以后的某點(diǎn)可以使用reset()方法重新將流定位到標(biāo)記處,隨后的讀取將返回從標(biāo)記初開始的數(shù)據(jù)。但是,從標(biāo)記處到重新將流定位點(diǎn)不能任意長。重新定位到標(biāo)記處之前答應(yīng)讀取的字節(jié)數(shù)就是由mark()的變量readAheadLimit決定。多長就會(huì)觸發(fā)IOException,而且任何指定時(shí)刻,輸入流中只可以有一個(gè)標(biāo)記,假如標(biāo)記了第二個(gè)標(biāo)記,就會(huì)覆蓋第一個(gè)標(biāo)記了。其實(shí)標(biāo)記和重新設(shè)置位置都是通過存儲(chǔ)從內(nèi)部緩沖區(qū)中的標(biāo)記位置讀出每一字節(jié)來實(shí)現(xiàn)。最麻煩的狀況是,并非所有輸入流都支持標(biāo)記和重新設(shè)置位置的。所以在設(shè)置之前要用markSupported()方法檢測一下。
實(shí)際上不支持標(biāo)記和重新設(shè)置位置的流多于支持它們的流。Elliotte Rusty Harold大師覺得這幾個(gè)方法設(shè)計(jì)的標(biāo)準(zhǔn)不高,將功能性與一個(gè)許多甚至可能是大部分子類都不可用的抽象超類結(jié)合是一個(gè)相當(dāng)拙劣的想法。最好是將這三個(gè)方法放在不同的接口中。提供類似于markSupported()方法在運(yùn)行時(shí)進(jìn)行功能性檢測是較為傳統(tǒng),非面向?qū)ο蟮慕鉀Q方法。面向?qū)ο蟮姆椒▽⑼ㄟ^接口和類把該方法嵌入到面向?qū)ο笙到y(tǒng)中,從而在編譯時(shí)檢測所有的流。
Java.io中總是支持標(biāo)記的輸入流:BufferedInputStream和ByteArrayInputStream。
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注