網絡程序所做的很大一部分工作只是輸入和輸出:從一個系統向另一個系統移動數據。
輸出流
Java的基本輸出流類是java.io.OutputStream:
public abstract class OutputStream
這個類提供了寫入數據所需的基本方法,包括:
public abstract void write(int b) throws IOException
public void write(byte[] data) throws IOException
public void write(byte[] data, int offset, int length) throws IOException
public void flush() throws IOException
public void close() throws IOException
Output子類使用這些方法向某種媒體寫入數據,如
FileOutputStream寫文件
TelnetOutputStream寫網絡連接
ByteArrayOutputStream寫擴展的字節數組
不管寫入哪種媒介,主要也只會使用這5種方法。
write(int b)方法只寫入8位,其余舍棄。
輸出流要加緩沖,要flush,要close。
流出以太網卡的每個TCP分片至少包含用于路由和糾錯等開銷的40個字節。如果一個字節一個字節的發送,會導致開銷很大。
字符自動生成程序:
每行72個字符,從33到126循環輸入。即
33~104
34~105
...
55~126
56~126 33
寫字節:
public static void generateCharacters(OutputStream out) throws IOException{int firstPrintableCharactor = 33;int numberOfPrintableCharacters = 94;int numberOfCharactersPerLine = 72;int start = firstPrintableCharacter;while(true){for (int i = start; i < start + numberOfCharacterPerLine; i++){out.write((i-firstPrintableCharacter)%numberOfPrintableCharacters+firstPrintableCharacter);}out.write("\r\n");start = ((start+1)-firstPrintableCharacter)%numberOfPrintableCharacters+firstPrintableCharacter;}
}
寫字節數組:
public static void generateCharacters(OutputStream out) throws IOException{int firstPrintableCharactor = 33;int numberOfPrintableCharacters = 94;int numberOfCharactersPerLine = 72;byte[] line = new byte[numberOfCharactersPerLine+2];int start = firstPrintableCharacter;while(true){for (int i = start; i < start + numberOfCharacterPerLine; i++){line[i-start] = (byte)((i-firstPrintableCharacter)%numberOfPrintableCharacters+firstPrintableCharacter);}line[72] = (byte)'\r';line[73] = (byte)'\n';out.write(line);start = ((start+1)-firstPrintableCharacter)%numberOfPrintableCharacters+firstPrintableCharacter;}
}
輸入流
Java的基本輸入類是java.io.InputStream
public abstract class InputStream
這個類提供了將數據讀取為原始字節所需的基本方法:
public abstract int read() throws IOException
public int read(byte[] input) throws IOException
public int read(byte[] input, int offset, int length) throws IOException
public long skip(long n) throws IOException
public int avaliable() throws IOException
public void close() throws IOException
FileInputStream讀文件
TelnetInputStream讀網絡連接
ByteArrayInputStream讀字節數組
read()方法會等待并阻塞其后任何代碼的執行,直到有一個字節的數據可用。
輸入輸出可能很慢,所以如果程序在進行其他重要的操作,請嘗試將I/O放在自己的線程中。
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;
}
常用技術:
int bytesRead = 0;
int bytesToRead = 1024;
byte[] input = new byte[bytesToRead];
while (bytesRead < bytesToRead){bytesRead += in.read(input, bytesRead, bytesToRead - bytesRead);
}
所有3個read()方法都用返回-1表示流的結束。
上面代碼存在一個bug,即在讀取1024個字節之前,流結束了。修正如下:
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;
}
另一種讀取方法是,有多少可用就讀取多少。
int bytesAvailable = in.available();
byte[] input = new byte[bytesAvailable];
int bytesRead = in.read(intput, 0, bytesAvailable);
//立即繼續程序的其他部分,此時bytesRead和bytesAvailable相等。
在流的最后,available()會返回0。
標記和重置
InputStream類還有3個不太常用的方法,允許程序備份和重新讀取已經讀取的數據。
public boolean markSupported()
public void mark(int readAheadLimit)
public void reset() throws IOException
markSupported()方法返回是否支持標記。
mark()方法標記流的當前位置,之后可用reset()把流重置到標記的位置。
一個流在任何時刻都只能有一個標記,標記第二個位置會清除第一個標記。
不能重置任意遠的位置,能重置多遠,取決于mark()的readAheadLimit參數。如果試圖重置太遠,會拋出IOException異常。
標記和重置通過將標記位置之后的所有字節存儲于內部緩沖區來實現。
如果流不支持標記,則mark()會什么都不做,而reset()將拋出一個IOException。
java.io中唯一兩個時鐘支持標記的輸入流是BufferedInputStream和ByteArrayInputStream。
而其他輸入流,如TelnetInputStream可能在首先連接到緩沖的輸入流時才支持標記。
過濾器流
Java提供了很多過濾器類,可以附加到原始流中,在原始字節和各種格式之間來回轉換。
過濾器有兩個版本:過濾器流及閱讀器和書寫器。
過濾器流仍然主要講原始數據作為字節操作;
閱讀器和書寫器處理各種編碼文本。
過濾器流置于原始流(如TelnetInputStream和FileOutputStream)或其他過濾器流之上。
閱讀器和書寫器置于原始流、過濾器流或其他閱讀器和書寫器之上。
過濾器流不能放在閱讀器和書寫器上面。
過濾器以鏈的形式進行組織。
每個過濾器輸出流都有與java.io.OutputStream相同的write()、close()和flush()方法;
每個過濾器輸入流都有與java.io.InputStream相同的read()、close()和available()方法。
將過濾器流連接在一起:
保存底層流的引用:
FileInputStream fin = new FileInputStream("data.txt");
BufferedInputStream bin = new BufferedInputStream(fin);
InputStream in = new FileInputStream("data.txt");
in = new BufferedInputStream(in);
DataOutputStream dout = new DataOutputStream(new BufferedOutputStream(new FileOutputStream("data.txt")));
緩沖流
BufferedOutputStream類將待寫入的數據存儲在緩沖區中(名為buf的保護字節數組字段),直到緩沖區滿或刷新輸出流。然后將數據一次全部寫入底層輸出流。
緩沖網絡輸出通常會帶來巨大的性能提升。
BufferedInputStream類也有作為緩沖區的名為buf的保護字節數組。調用某個read()方法時,首先嘗試從緩沖區獲得請求的數據。當緩沖區沒有數據時,流從底層的源中讀取。這時,它會從源中讀取盡可能多的數據存入緩沖區,而不管它是否馬上需要所有這些數據。
對于網絡連接,提升性能的效果不明顯,這時的瓶頸往往是網絡發送數據的速度。緩沖輸入不會有壞處,隨著網絡的速度加快會變得更為重要。
兩個構造參數:
public BufferedInputStream(InputStream in) ?//默認大小2048Byte
public BufferedInputStream(InputStream in, int bufferSize)
public BufferedOutputStream(OutputStream out) ?//默認大小512Byte
public BufferedOutputStream(OutputStream out, int bufferSize)
最優緩沖區大小取決于所緩沖的流是何種類型。
UDP不超過8K,TCP不超過1K。
BufferedInputStream只是覆蓋了InputStream的方法。支持標記和重置。
public int read() throws IOException
public int read(byte[] input, int offset, int length) throws IOException
public long skip(long n) throws IOException
public int available() throws IOException
public boolean markSupported()
public void mark(int readLimit)
public void reset() throws IOException
BufferedOutputStream覆蓋了OutputStream的3個方法。
public void write(int b) throws IOException
public void write(byte[] data, int offset, int length) throws IOException
public void flush() throws IOException
PrintStream
public PrintStream(OutputStream out)
public PrintStream(OutputStream out, boolean autoFlush)
默認需要顯式刷新。如果autoFlush為true,在每次寫入一個字節數組或換行或調用println()時刷新。
除write()、flush()、close()方法外,還重載了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[] text)
public void print(String s)
public void print(Object o)
public void println()
public void println(boolean b)
public void println(char c)
public void println(int i)
public void println(long l)
public void println(float f)
public void println(double d)
public void println(char[] text)
public void println(String s)
public void println(Object o)
每個print()方法都將其參數以可預見的方式轉換為字符串,再用默認的編碼方式把字符串寫入底層輸出流。
println()方法進行相同的操作,并在默認添加一個平臺有關的分割字符。Linux下是\n,Windows下是\r\n。
PrintStream不提供改變默認編碼的機制。
PrintStream吞掉了所有異常。
PrintStream有害,網絡程序員不應使用。
PushbackInputStream
數據流
DataInputStream和DataOutputStream類提供了可以用二進制格式讀寫Java的簡單數據類型和字符串的方法。
所用的二進制格式主要用于在兩個不同的Java程序之間交換數據,而無論是通過網絡連接、數據文件、管道還是其他媒介。
輸出流寫入什么數據,輸入流就讀取什么數據。
DataOutputStream類提供了11個寫入某種Java數據類型的方法:
public final void writeBoolean(boolean b) throws IOException
public final void writeByte(int b) throws IOException
public final void writeShort(int s) throws IOException
public final void writeChar(int c) throws IOException
public final void writeInt(int i) throws IOException
public final void writeLong(long l) throws IOException
public final void writeFloat(float f) throws IOException
public final void writeDouble(double d) throws IOException
public final void writeChars(String s) throws IOException
public final void writeBytes(String s) throws IOException
public final void writeUTF(String s) throws IOException
DataInputStream和DataOutputStream是相生的。
public final boolean readBoolean() throws IOException
public final byte readByte() throws IOException
public final short readShort() throws IOException
public final char readChar() throws IOException
public final int readInt() throws IOException
public final long readLong() throws IOException
public final float readFloat() throws IOException
public final double readDouble() throws IOException
public final String readUTF() throws IOException
DataInputStream提供了兩個讀取C程序寫入的二進制數據的方法,可以讀取無符號字節和無符號短整數。
public final int readUnsignedByte() throws IOException
public final int readUnsignedShort() throws IOException
public final int read(byte[] input) throws IOException
public final int read(byte[] input, int offset, init length) throws IOException
public final void readFully(byte[] input) throws IOException
public final void readFully(byte[] input, int offset, int length) throws IOException
readFully()方法,重復的從底層輸入流讀取數據放在數組中,直到讀取了所請求數量的字節為止;如果沒有讀取到足夠的數據,拋出異常。
應用場景:讀取了HTTP首部中的Content-length字段,知道有多少字節的數據。
public final String readLine() throws IOException
讀取用行結束符分隔的一行文本,并返回一個字符串。
此方法廢棄,存在bug。它在大多數情況不能正確的將非ASCII字符轉換為字節。
這項任務由BufferedReader類的readLine()方法處理。
readLine()只識別換行或回車換行對。
如果回車是流的最后一個字符,則readLine()方法會掛起。
壓縮流
java.util.zip包中包含壓縮和解壓zip、gzip和deflate格式流的過濾器流。
除廣泛應用于文件外,這個包還允許Java應用程序通過網絡輕松地交換壓縮數據。
HTTP1.1包括了對壓縮文件傳輸的支持,其中服務器壓縮文件,而瀏覽器解壓文件。
輸入流解壓數據,輸出流壓縮數據。
public class DeflaterOutputStream extends FilterOutputStream
public class InflaterInputStream extends FilterInputStream
public class GZIPOutputStream extens FilterOutputStream
public class GZIPInputStream extends FilterInputStream
public class ZipOutputStream extends FilterOutputStream
public class ZipInputStream extends FilterInputStream
上述類都使用相同的壓縮算法,不同之處在于各種常量和包含于壓縮數據中的元信息。
此外zip流可能包含多個壓縮文件。
FileInputStream fin = new FileInputStream("allnames.gz");
GZIPInputStream gzin = new GZIPInputStream(fin);
FileOutputStream fout = new FileOutputStream("allnames");
int b = 0;
while((b = gzin.read()) != -1)fout.write(b);
gzin.close();
out.flush();
out.close();
或者直接
InputStream in = new GZIPInputStream(new FileInputStream("allnames.gz"));
ZipInputStream和ZipOutputStream有點復雜,因為zip文件實際上是包含多個項的歸檔文件,每個項都必須分別讀取。
zip歸檔文件中的每個文件都表示為一個ZipEntry對象,對象的getName()方法會返回最初的文件名。
FileInputStream fin = new FileInputStream("shareware.zip");
ZipInputStream zin = new ZipInputStream(fin);
ZipEntry ze = null;
int b = 0;
while((ze = zin.getNextEntry()) != null){FileOutputStream fout = new FileOutputStream(ze.getName());while((b = zin.read()) != -1)fout.write(b);zin.closeEntry();fout.flush();fout.close();
}
zin.close();
摘要流
java.util.security包中包含了兩個過濾器流,可以計算流的消息摘要。
DigestInputStream和DigestOutputStream
消息摘要在Java中用java.util.MessageDigest類表示,它是流的強散列碼,是一個很大的整數(一般為二進制格式的20個字節長),從任何長度的流中計算得出。
消息摘要可用于數字簽名,檢測數據是否在網絡傳輸中遭到破壞。
首先構建一個使用某種算法(如安全散列算法,SHA)的MessageDigest對象;
將這個MessageDigest對象和要加摘要的流一起傳遞給DigestOutputStream構造函數。
getMessageDigest()方法可獲取MessageDigest對象。
MessageDigest對象的digest()方法結束計算實際的摘要。
MessageDigest sha = MessageDigest.getInstance("SHA");
DigestOutputStream dout = new DigestOutputStream(out, sha);
byte[] buffer = new byte[128];
while(true){int byteesRead = in.read(buffer);if (bytesRead < 0)break;dout.write(buffer, 0, bytesRead);
}
dout.flush();
dout.close();
byte[] result = dout.getMessageDigest().digest();
閱讀器和書寫器
處理字符
java.io.Reader類
java.io.Writer類
最重要的具體子類是InputStreamReader和OutputStreamWriter類。
還有幾個不需要直接從底層輸入流讀取字符的原始閱讀器和書寫器類:
FileReader
FileWriter
StringReader
StringWriter
CharArrayReader
CharArrayWriter
前兩個類可以處理文件,后四個由Java內部使用,在網絡編程中不常用。
書寫器
Writer類是一個抽象類,有5個write()方法,以及flush()和close()方法。
protected Writer()
protected Writer(Object lock)
public abstract void write(char[] text, int offset, int length) throws IOException
public void write(int c) throws IOException
public void write(char[] text) throws IOException
public void wirte(String s) throws IOException
public void write(String s, int offset, int length) throws IOException
public abstract void flush() throws IOException
public abstract void close() throws IOException
OutputStreamWriter
從Java程序接收字符,根據指定的編碼方式將這些字符轉換為字節,寫入底層輸出流中。
public OutputStreamWriter(OutputStream out, String encoding) throws UnsupportedEncodingException
public OutputStreamWriter(OutputStream out)
public String getEncoding()
閱讀器
protected Reader()
protected Reader(Object lock)
public abstract int read(char[] text, int offset, int length) throws IOException
public int read() throws IOException
public int read(char[] text) throws IOException
public long skip(long n) throws IOException
public boolean ready()
public boolean markSupported()
public void mark(int readAheadLimit) throws IOException
public void reset() throws IOException
public abstract void close() throws IOException
read(char[] text, int offset, int length)是基本方法。
ready()方法與InputStream的available()的用途相同,判斷是否可以讀取數據。
InputStreamReader
是Reader的最重要的具體子類。
從底層輸入流(如FileInputStream或TelnetInputStream)中讀取字節。根據指定的編碼方式將這些字節轉換為字符。
public InputStreamReader(InputStream in)
public InputStreamReader(InputStream in, String encoding) throws UnsupportedEncodingException
使用MacCyrillic編碼方式將其全部轉換為Unicode字符串:
public static String getMacCyrillicString(InputStream in) throws IOexception{InputStreamReader r = new InputStreamReader(in, "MacCyrillic");StringBuffer sb = new StringBuffer();int c;while((c = r.read()) != -1)sb.append((char)c);r.close();return sb.toString();
}
過濾器閱讀器和書寫器
InputStreamReader和OutputStreamWriter類就像裝飾器,位于輸入流和輸出流之上,把面向字節的接口更改為面向字符的接口。
完成之后,其他面向字符的過濾器就可以放在使用java.io.FilterReader和java.io.FilterWriter類的閱讀器或書寫器上面。
包括:
BufferedReader
BufferedWriter
LineNumberReader
PushbackReader
PrintWriter
BufferedReader和BufferedWriter
默認緩沖區大小為8192字符
public BufferedReader(Reader in, int bufferSize)
public BufferedReader(Reader in)
public BufferedWriter(Writer out, int bufferSize)
public BufferedWriter(Writer out)
public static String getMacCyrillicString(InputStream in) throws IOexception{InputStreamReader r = new InputStreamReader(in, "MacCyrillic");r = new BufferedReader(r, 1024);StringBuffer sb = new StringBuffer();int c;while((c = r.read()) != -1)sb.append((char)c);r.close();return sb.toString();
}
LineNumberReader
是BufferedReader的一個記錄當前行號的子類。
getLineNumber()獲得。
public int getLineNumber()
默認情況下,第一個行號為0。
當前行號和所有其后的行號可以用setLineNumber()方法改變:
public void setLineNumber(int lineNumber)
兩個構造函數
public LineNumberReader(Reader in)
public LineNumberReader(Reader in, int bufferSize)
默認大小為8192字符。
PushbackReader
與PushbackInputStream類相似。
public void unread(int c) throws IOException
public void unread(char[] text) throws IOException
public void unread(char[] text, int offset, int length) throws IOException
默認回壓緩沖區的大小只有一個字符。
public PushbackReader(Reader in)
public PushbackReader(Reader in, int bufferSize)
PrintWriter
寫入字符而不是字節
如果底層的書寫器能夠正確處理字符集轉換,那么PrintWriter的所有方法也能處理這種轉換。
PrintWriter out = new PrintWriter(“myfile.txt”); //寫文件