Java網絡編程的Java流介紹

前言

網絡程序所作的很大一部分工做都是簡單的輸入輸出:將數據字節從一個系統移動到另外一個系統。Java的I/O創建於流(stream)之上。輸入流讀取數據,輸出流寫入數據。過濾器流(filter)流能夠串聯到輸入或輸出流上。讀寫數據時過濾器能夠修改數據(加密或壓縮),或者只是提供額外的方法,將讀/寫的數據轉換爲其餘格式。閱讀器(reader)和書寫器(writer)能夠串鏈到輸入流和輸出流上,容許程序讀/寫文本而不是字節。java

輸出流

Java的基本輸出流類是:java.io.OutputStream;程序員

這個類中提供了寫入數據所需的基本方法,以下:編程

public abstract void write(int b) throws IOException;
public void write(byte b[]) throws IOException
public void write(byte b[], int off, int len) throws IOException
public void flush() throws IOException
public void close() throws IOException

可是咱們平時使用它的子類來實現向某種特定介質寫入數據。例如:FileOutputStream等,它的子類都是經過裝飾模式來實現一些特定的功能的。OutputStream的基本方法是write(int b)。這個方法接受一個0到255之間的整數做爲參數,將對應的字節寫入到輸出流中。雖然此方法接受一個int做爲參數,但它實際上會寫入一個無符號字節,由於java沒有無符號字節數據類型,因此這要使用int來代替。無符號字節和有符號字節之間惟一的真正區別在於解釋。它們都由8個二進制組成,write方法將int寫入一個網絡鏈接時,線纜上只會放8個二進制位。若是將一個超出0~255的int傳入write方法,將協議這個數的最低字節,其餘3個字節將被忽略。由於每次寫入一個字節效率不高,因此就又提供了兩個能夠傳入字節數組的方法,write(byte[])、write(byte b[],int off,int len)。數組

與網絡硬件中緩存同樣,流還能夠在軟件中獲得緩衝,即直接用java代碼緩存。在寫入數據完成後,刷新(flush)輸出流很是重要。由於flush()方法能夠強迫緩衝的流發送數據,即便緩衝區尚未滿,以此來打破流一直等待着緩衝區滿了纔會發送數據的狀態。緩存

最後,當結束一個流操做時,要經過調用它的close()方法將其關閉。關閉流會釋放與整個流關聯的全部資源,若是流來自網絡鏈接,這個鏈接也會被關閉。長時間未關閉一個流,可能會泄漏文件句柄、網絡端口和其餘資源。因此在Java6以及更早的版本中,是在一個finally塊中關閉流。可是Java7引入了try width resources 能夠簡化關閉流的操做,只須要把流定義在try的參數中便可。網絡

以下所示:函數

try(OutputStream out = new FileOutputStream("D:/temp/test.txt")){
     // 處理輸出流

}catch (IOException e){
    e.printStackTrace();
}

由於Java會對try塊參數表中 聲明的全部AutoCloseable對象自動調用close()。Java中的流相關的類基本上都直接或間接的實現了AutoCloseable接口。性能

輸入流

Java的基本輸出流類是:java.io.InputStream;測試

這個類提供了將數據讀取爲原始字節所須要的基本方法。以下:this

public abstract int read() throws IOException;
public int read(byte b[]) throws IOException
public int read(byte b[], int off, int len) throws IOException
public long skip(long n) throws IOException 
public int available() throws IOException 
public void close() throws IOException

 InputStream的基本方法是沒有參數的read()方法。此方法從輸入流的源中讀取1字節數據,做爲一個0到255的int返回,流的結束經過返回-1來表示。read()方法會等待並阻塞其後任何代碼的執行,直到有1字節的數據可供讀取。輸入和輸出可能很慢,因此若是成行在作其餘重要工做,要儘可能將I/O放在單獨的線程中。

一次讀取1字節的效率也不高,所以,有兩個重載的read()方法,能夠用從流中讀取的多字節的數據填充一個指定的數組:read(byte[] input)和read(byte[] input, int offset,int length)。當read的時候若是遇到IOException或網絡緣由只讀取到了一部分,這個時候就會返回實際讀取到的字節數。

例如:

int bytesRead = 0;
int bytesToRead = 1024;
byte[] input = new byte[bytesToRead];
while (bytesRead<bytesToRead){
       bytesRead += in.read(input,bytesRead,bytesToRead - bytesRead);
}

上面這段代碼就是沒有考慮到有可能流會中斷致使讀取的數據永遠讀不出來,因此要防止這種事情出現須要先測試read()的返回值,而後再增長到byteRead中

以下所示:

int bytesRead = 0;
int bytesToRead = 1024;
byte[] input = new byte[bytesToRead];
while (bytesRead<bytesToRead){
  int result = in.read(input,bytesRead,bytesToRead - bytesRead);
  if(result == -1) break;
  bytesRead += result;
}

可使用available()方法來肯定不阻塞的狀況下有多少字節能夠讀取。它會返回可讀取的最少字節數。事實上還能讀取更多字節,至少能夠讀取available()建議的字節數。

以下:

int bytesAvailable = in.available();
byte[] input = new byte[bytesAvailable];
int bytesRead = in.read(input,0,bytesAvailable);
//讀取到數據後,去執行其餘部分

在少數狀況下,你可能但願跳過數據不進行讀取。skip()方法會完成這項任務。

與輸出流同樣,一旦結束對輸入流的操做,應當調用close()方法將其關閉。這會釋放這個流關聯的全部資源。

InputStream類中還有3個不常常用的方法,

public synchronized void mark(int readlimit)
public synchronized void reset() throws IOException
public boolean markSupported()

爲了從新讀取數據,要用mark()方法標記流的當前位置,在之後某個時刻能夠用reset()方法把流重置到以前標記的位置。在嘗試使用標記和重置以前,要堅持markSupported()方法是否返回true。若是返回true,那麼這個流確實支持標誌和重置,不然,mark()會什麼都不作,而reset()將拋出一個IOException異常。

過濾器流

過濾器由兩個版本:過濾器流(filte stream)以及閱讀器(reader)和書寫器(writer)

每一個過濾器輸出流都有與java.io.OutputStream相同的write()、close()和flush()方法。每一個過濾器輸入流都有與java.io.InputStream相同的read()、close()和available()方法。

過濾器經過其構造函數與流鏈接。

FileInputStream iin = new FileInputStream("test.txt");
BufferedInputStream bin = new BufferedInputStream(iin);

這種狀況下若是混合調用鏈接到同一個源的不一樣流,這可能會違反過濾器流的一些隱含約定。大多數狀況下應當只使用鏈中最後一個過濾器進行實際的讀/寫。

能夠用以下方式:

InputStream iin = new FileInputStream("test.txt");
iin = new BufferedInputStream(iin);

緩衝流

BufferedOutputStream類將寫入的數據存儲在緩衝區中,直到緩衝區滿了或者執行了flush方法。而後將數據一次所有寫入底層輸出流。在網絡鏈接中,緩衝網絡輸出一般會帶來巨大的性能提高。

BufferedInputStream類也有一個做爲緩衝區的保護字節數組,當調用某個流的read()方法時,它首先嚐試從緩衝區得到請求的數據。當緩衝區沒有數據時,流才從底層的源中讀取數據。這時,它會讀取儘量多的數據存入緩衝區,而不論是否立刻須要全部這些數據。不會當即用到的數據能夠在之後調用read()時讀取。當從本地磁盤中讀取文件時,從底層流中讀取幾百字節的數據與讀取1字節數據幾乎同樣快。所以,緩衝能夠顯著提高性能。

BufferedOutputStream有兩個構造函數,BufferedInputStream也是有兩個構造函數:

public BufferedInputStream(InputStream in)
public BufferedInputStream(InputStream in, int size) 

public BufferedOutputStream(OutputStream out)
public BufferedOutputStream(OutputStream out, int size) 

PrintStream

PrintStream類是大多數程序員都會遇到的第一個過濾器輸出流,由於System.out就是一個PrintStream。還可使用下面兩個構造函數將其餘輸出流串鏈到打印流:

public PrintStream(OutputStream out)
public PrintStream(OutputStream out, boolean autoFlush)

若是autoFlush參數爲true,那麼每次寫入1字節數組或換行,或者調用println()方法時,都會刷新輸出流。除了日常的write()、flush()和close()方法,PrintStream還有9個重載的print()方法和10個重載的println方法:

public void print(boolean b)
public void print(char c)
public void print(int i)
public void print(long l)
public void print(float f)
public void print(double d)
public void print(char s[])
public void print(String s) 
public void print(Object obj)
public void println()
public void println(boolean x)
public void println(char x) 
public void println(int x)
public void println(long x) 
public void println(float x) 
public void println(double x) 
public void println(char x[])
public void println(String x)
public void println(Object x) 

每一個print()方法都將其參數以可見的方式轉換爲一個字符串,再用默認的編碼方式把字符串寫入底層輸出流。println()方法也完成相同操做,但會在所寫的行末尾追加一個與平臺有關的行分隔符。

在網絡編程中應儘可能避免使用PrintStream。

PrintStream第一個問題,println()輸出是與平臺有關的。

PrintStream第二個問題,會假定使用所在平臺的默認編碼方式。

PrintStream第三個問題,會吞掉了全部異常。

數據流

DataInputStream和DataOutputStream類提供了一些能夠用二進制格式讀/寫Java的基本數據類型和字符串。

DataOutputStream類提供下面11種方法,能夠寫入特定的Java數據類型。

public final void writeBoolean(boolean v) throws IOException 
public final void writeByte(int v) throws IOException
public final void writeShort(int v) throws IOException
public final void writeChar(int v) throws IOException
public final void writeInt(int v) throws IOException
public final void writeLong(long v) throws IOException
public final void writeFloat(float v) throws IOException
public final void writeDouble(double v) throws IOException 
public final void writeBytes(String s) throws IOException
public final void writeChars(String s) throws IOException 
public final void writeUTF(String str) throws IOException

前面的8個方法,都按照實際參數的類型長度來寫數據的,最後三個方法有些特別,writeChars()方法只是對String參數迭代處理,將各個字符按順序寫爲一個2字節的big-endian Unicode字符。writeBytes()方法迭代處理String參數,但只是寫入每一個字符的低字節。

writeChars和writeBytes都不會對輸出流的字符串的長度編碼。所以,你沒法真正區分原始字符和做爲字符串一部分的字符。writeUTF()方法則包括了字符串的長度。它將字符串自己用Unicode UTF-8編碼的一個變體進行編碼。

除了這些寫入二進制數字和字符串的方法,DataOutputStream固然還有全部OutputStream類都有的日常的write()、flush()、和close()方法。

DataInputStream與DataOutputStream是互補的。DataOutputStream寫入的每一種數據格式,DataInputStream均可以讀取。此外DataInputStream還有一般read()、available()、skip()和close()方法,以及讀取整個字節數組和文本行的方法。因此DataInputStream的內容就不寫了。

書寫器

Writer是以字符流的方式書寫數據,它是一個抽象類,有兩個保護類型的構造函數。與OutputStream相似,Writer類從不直接使用;相反,會經過他的某個子類以多態方式使用。它有5個write()方法,另外還有flush()和close()方法。

protected Writer()
protected Writer(Object lock) 
abstract public void write(char cbuf[], int off, int len) throws IOException
public void write(int c) throws IOException
public void write(char cbuf[]) throws IOException 
public void write(String str) throws IOException 
public void write(String str, int off, int len) throws IOException 
bstract public void flush() throws IOException
abstract public void close() throws IOException

abstract public void write(char cbuf[], int off, int len)方法是基礎方法,其餘四個write()都是根據它實現的。子類至少覆蓋整個方法以及flush()和close(),
可是爲了提供更高效的實現方法,大多數子類還覆蓋了其餘一些write()方法。

例如:給定一個Writer對象w,能夠這樣寫入字符串「Jimoer」:

char[] jimoer = {'J','i','m','o','e','r'};
w.write(jimoer,0,jiomer.length);

也能夠用其餘write()方法完成一樣的任務:

Writer w = new OutputStreamWriter(out);
w.write(jiomer);
for(int i=0;i<jimoer.length;i++){
     w.write(jiomer[i]);
}
w.write("Jimoer");
w.write("Jimoer",0,5);

全部這些例子表述都是一樣的事情,只不過方式有所不一樣。

書寫器能夠緩衝,有可能直接串鏈到BufferedWriter,也有可能直接鏈入。爲了強制將一個寫入提交給輸出介質,須要調用flush()方法。

OutputStreamWriter

OutputStreamWriter是Writer的最重要的具體子類。OutputStreamWriter會從Java程序中接收字符。會根據指定的編碼方式將這些字符轉換爲直接,並寫入底層輸出流。

構造函數指定了要寫入的輸出流和使用的編碼方式:

public OutputStreamWriter(OutputStream out, String charsetName)
        throws UnsupportedEncodingException
    {
        super(out);
        if (charsetName == null)
            throw new NullPointerException("charsetName");
        se = StreamEncoder.forOutputStreamWriter(out, this, charsetName);
    }

除了構造函數,OutputStream只有一般的Writer方法,還有一個返回對象編碼方式的方法:

public String getEncoding()

閱讀器

Reader是一個抽象類,從不直接使用,只經過子類來使用。有三個read()方法,另外還有skip()、close()、ready()、mark()、reset()和markSupported()方法:

protected Reader()
protected Reader(Object lock) 
abstract public int read(char cbuf[], int off, int len) throws IOException
public int read() throws IOException 
public int read(char cbuf[]) throws IOException
public long skip(long n) throws IOException
public boolean ready() throws IOException
public boolean markSupported()
public void mark(int readAheadLimit) throws IOException 
public void reset() throws IOException 
abstract public void close() throws IOException

這裏面的方法大多數都與Writer中方法能對應上,read()方法以int型(從0到65,535)返回一個單一的Unicode字符或讀到流結束時返回-1。

InputStreamReader是Reader的最重要的具體子類。InputStreamReader從其底層輸入流中讀取字節。而後根據指定的編碼發那個仍是將字節轉爲字符,並返回這些字符。

構造函數以下:

public InputStreamReader(InputStream in) 
public InputStreamReader(InputStream in, String charsetName)
        throws UnsupportedEncodingException
public InputStreamReader(InputStream in, Charset cs) 
public InputStreamReader(InputStream in, CharsetDecoder dec)

若是沒有指定編碼方式,就使用平臺的默認編碼方式。若是指定了一個位置的編碼方式,會拋出UnsupportedEncodingException異常。

過濾閱讀器和書寫器

InputStreamReader和OutputStreamWriter類就至關於輸入和輸出流之上的裝飾器,把面向字節的接口改成面向字符的接口。完成以後就能夠將其餘面向字符的過濾器放在使用java.io.FilterReader和java.io.FilterWriter類的閱讀器或書寫器上。

BufferedReader和BufferedWriter也有與閱讀器和書寫器關聯的經常使用方法,如read()、ready()、write()和close()。這兩個類都有兩個構造函數,能夠將BufferedReader或BufferedWriter串鏈到一個底層閱讀器或書寫器,並設置緩衝區的大小。若是沒有設置大小,則使用默認的大小8192字符:

public BufferedReader(Reader in, int sz) 
public BufferedReader(Reader in)
public BufferedWriter(Writer out)
public BufferedWriter(Writer out, int sz) 

BufferedReader類還有一個readLine()方法,它讀取一行文本,並做爲一個字符串返回:

public String readLine() throws IOExceptioin

這個方法能夠替代DataInputStream中國已經廢棄的readLine()方法,它與該方法的行爲基本相同。主要區別在於,經過BufferedReader串鏈到InputStreamReader,能夠用正確的字符集讀取行,而不是採用平臺的默認編碼方式。

BufferedWriter類增長了一個其超類所沒有的新方法,名爲newLine(),也用於寫入一行:

public void newLine() throws IOException

這個方法向輸出插入一個與平臺有關的行分隔符字符串。

PrintWriter

PrintWriter類用戶取代Java1.0的PrintStream類,它能正確地處理多字節字符集和國際化文本。除了構造函數,PrintWriter類也有與PrintStream幾乎相同的方法集。

public PrintWriter (Writer out)
public PrintWriter(Writer out,boolean autoFlush) 
public PrintWriter(OutputStream out)
public PrintWriter(OutputStream out, boolean autoFlush) 
public PrintWriter(String fileName) throws FileNotFoundException 
private PrintWriter(Charset charset, File file) throws FileNotFoundException
public PrintWriter(String fileName, String csn)
        throws FileNotFoundException, UnsupportedEncodingException
public PrintWriter(File file) throws FileNotFoundException 
public PrintWriter(File file, String csn)
        throws FileNotFoundException, UnsupportedEncodingException
public void flush() 
public void close()
public boolean checkError() 
public void write(int c) 
public void write(char buf[], int off, int len)
public void write(char buf[])
public void write(String s, int off, int len) 
public void write(String s)
public void print(boolean b)
public void print(char c)
public void print(int i)
public void print(long l)
public void print(float f)
public void print(double d)
public void print(char s[])
public void print(String s)
public void print(Object obj)
public void println()
public void println(boolean x)
public void println(char x)
public void println(int x) 
public void println(long x) 
public void println(float x) 
public void println(double x) 
public void println(char x[])
public void println(String x)
public void println(Object x) 
public PrintWriter printf(String format, Object ... args) 

這些方法的行爲大多數與PrintStream中相同。只有4個write()方法有所例外,它們寫入字符而不是字節。

相關文章
相關標籤/搜索