最近在重拾Java網絡編程,想要了解一些JAVA語言基本的實現,這裏記錄一下學習的過程。java
網絡節點(node)
:位於網絡上的互相連通的設備,一般爲計算機,也能夠是打印機,網橋,路由器等等燒入網卡,從而確保沒有任何兩臺設備的MAC地址是相同的。IP地址
:網絡地址,由ISP供應商決定。包交換網絡(packet-switched network)
:數據被拆解爲多個包並分別在線路上傳輸,每一個包包含發送者和接收者的信息。協議
:節點之間進行交流所遵循的規定網絡分層模型
:node
當咱們試圖訪問一個網站時,瀏覽器實際上直接訪問的是本地的傳輸層(Transport Layer)。傳輸層將HTTP報文分段爲TCP報文並繼續向下傳遞給IP層。IP層封裝信息而且將其傳送到物理鏈路上。服務器對收到的數據以相反的順序解包並讀取裏面的請求內容。面試
端口
:一臺計算機在傳輸層的每個協議上一般有65,535個邏輯端口。HTTP一般使用80端口C/S模型
:客戶端與服務器模型。一般是客戶端向服務器主動發送請求並等待服務器的響應。編程
Java的IO最初是基於流的。從輸入流中讀取數據,向輸出流中寫入數據。不一樣類型的流入java.io.FileInputStream
,sun.net.TelnetOutput Stream
每每對應於不一樣類型的流數據。可是,讀取和寫入流的方法本質上是相同的。Java還提供了Readers
和Writers
系列來支持對字符流的輸入輸出。數組
流是同步的。同步的流是指當程序(一般是某個線程)要求從流中讀取或是向流中寫入一段數據時,在獲取到數據或是完成數據寫入以前,該程序將會一直停在這一步,不會進行任何工做。Java也提供了非阻塞的I/O。咱們將在後面繼續瞭解。瀏覽器
先看一下全部輸出流的父類OutputStream
的API緩存
public abstract class OutputStream{ //核心方法 //各個不一樣的流將實現具體的寫入 //如ByteArrayOutputStream能夠直接寫入內存,而FileOutputStream則須要根據操做系統調用底層函數來寫入文件 public abstract void write(int b); 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; }
在這裏write(int)
是核心方法,它會將一個個ASCII碼寫入輸出流中。可是,每寫一個字節就傳送的浪費是巨大的,由於一次TCP/UDP傳輸須要攜帶額外的控制信息和路由信息。因此一般會將字節緩存到必定數量後再發送。服務器
使用完輸出流後,及時的關閉它是一個很好的習慣,不然可能會形成資源的浪費或是內存泄漏。咱們一般使用finally
塊來實現:微信
OutputStream os = null; try{ os = new FileOutputStream("/tmp/data.txt"); }catch(IOException e){ System.err.println(ex.getMessage()); }finally{ if(os!=null){ try{ os.close(); }catch(IOException e){ //處理異常 } } }
上面的代碼風格被稱爲dispose pattern
,在使用須要回收資源的類時都會使用這個模式。Java7之後咱們能夠用另外一種更加簡潔的方式來實現這個代碼:網絡
try(OutputStream os = new FileOutputStream("/tmp/data.txt")){ ... }catch(IOException e){ System.err.println(ex.getMessage()); }
Java會自動調用實現AutoCloseable
對象的close()
方法,所以咱們無需再寫ugly的finally塊。
一樣,看一下輸入流的基類InputStream
的API
public abstract class InputStream{ //核心方法 //從輸入流中讀取一個字節並將其做爲int值(0~255)返回 //當讀到輸入流末尾時,會返回-1 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 available() throws IOException; public void close() throws IOException; }
輸入流也是阻塞式的,它在讀取到字節以前會等待,所以其後的代碼將不會執行知道讀取結束。
爲了解決阻塞式讀寫的問題,能夠將IO操做交給單獨的線程來完成。
read()
方法返回的應該是一個byte,可是它卻返回了int類型,從而致使其返回值變爲-128~127
之間而不是0~255
。咱們須要經過必定的轉化來獲取正確的byte類型:
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>=0? b : b+256); }
有時候咱們沒法在一次讀取中得到全部的輸入,因此最好將讀取放在一個循環中,在讀取完成以後再跳出循環。
int bytesToRead = 1024; int bytesRead = 0; byte[] buffer = new byte[bytesToRead]; while(bytesRead < bytesToRead){ int tmp = inputStream.read(buffer, bytesRead, bytesToRead-bytesRead); //當流結束時,會返回-1 if(tmp == -1) break; bytesRead += tmp; }
Java IO採用了裝飾者模式,咱們能夠將一個又一個裝飾器加到當前流上,賦予該流新的解析。
在這裏,先經過TelnetInputStream
從網絡上獲取Telnet數據流,而後再逐個通過多個過濾器從而得到流中的數據。將過濾器相連的方法很簡單:
FileInputStream fin = new FileInputStream("data.txt"); BufferedInputStream bin = new BufferedInputStream(fin);
Buffered Stream
先將數據緩存至內存,再在flush或是緩存滿了之後寫入底層。
大多數狀況下,緩存輸出流能夠提升性能,可是在網絡IO的場景下不必定,由於此時的性能瓶頸取決於網速,而不是網卡將數據傳到上層的速度或是應用程序運行的速度。
構造器以下:
public BufferedInputStream(InputStream in); public BufferedInputStream(InputStream in, int bufferSize); public BufferedOutputStream(OutputStream out); public BufferedOutputStream(OutputStream out, int bufferSize);
PrintStream
輸出流,System.out
就是一個PrintStream
。默認狀況下,咱們須要強制flush將PrintStream
中的內容寫出。可是,若是咱們在構造函數中將自動flush設置爲true,則每次寫入一個byte數組或是寫入換行符或是調用println操做都會flush該流。
public PrintStream(OutputStream out) public PrintStream(OutputStream out, boolean autoFlush)
可是,在網絡環境中應當儘量不使用PrintStream,由於在不一樣的操做系統上,println的行爲不一樣(由於換行符的標記不一樣)。所以一樣的數據在不一樣的操做系統上可能不一致。
除此之外,PrintStream依賴於平臺的默認編碼。可是,這個編碼和服務器端期待的編碼格式極可能是不同的。PrintStream不提供改變編碼的接口。
並且,PrintStream會吞掉全部的異常。咱們沒法對異常進行相應的編碼。
Date Stream
以二進制數據的格式讀取和寫入Java的基本數據類型和String類型。
DataOutputStream
的API:
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 //根據UTF-16編碼將其轉化爲長度爲兩個字節的字符 public final void writeChars(String s) throws IOException //只存儲關鍵的信息,任何超出Latin-1編碼範圍的內容都將會丟失 public final void writeBytes(String s) throws IOException //上面兩個方法都沒有將字符串的長度寫入輸出流,因此沒法分辨究竟原始字符仍是構成字符串的最終字符 //該方法採用UTF-8格式編碼,而且記錄的字符串的長度 //它應當只用來和其它Java的程序交換信息 public final void writeUTF(String s) throws IOException
DataInputStream
的API:
public final boolean readBoolean() throws IOException public final byte readByte() throws IOException public final char readChar() throws IOException public final short readShort() 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 //讀取別的程序寫的unsigned類型數據,如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, int length) throws IOException //完整的讀取必定長度的字符串,若是可讀的長度不足,將拋出IOException //可用於已知讀取長度的場景,如讀取HTTP報文。 咱們可使用Header中的content-length屬性來讀取相應長度的body public final void readFully(byte[] input) throws IOException public final void readFully(byte[] input, int offset, int length) throws IOException //讀取一行 可是最好不要使用,由於它沒法正確的將非ASCII碼轉化爲字符串 public final String readLine() throws IOException
這裏須要強調一下爲何不要使用readLine()
方法。由於readLine方法識別一行末尾的方法是經過\r
或是\r\n
。當readLine遇到\r
時,它會判斷下一個字符是否是\n
。若是是,則將兩個標記都拋棄而且將以前的內容做爲一行返回。若是不是,則拋棄\r
並將以前的內容返回。問題在於,若是流中最後一個字符爲\r
,那麼讀取一行的方法會掛起,並等待下一個字符。
這個問題在網絡IO中特別明顯,由於當一次數據發送結束以後,客戶端在關閉鏈接以前會等待服務器端的響應。服務器端卻在等待一個不存在的輸入。所以兩者陷入死鎖。若是幸運的話,客戶端會由於超時斷開鏈接,使得死鎖結束,同時你丟失了最後一行數據。也有可能這個程序無限死鎖下去。
文本中的字符並不能和ASCII碼徹底劃等號。不少國家的語言如中文,日文,韓文等都遠遠超出了ASCII碼編碼的範圍。用ASCII碼是沒法識別這些字節的。所以JAVA推出了Reader和Writer類。它將根據特定的編碼來解讀字節。
Writer
類API
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 write(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
這裏和以前的區別在於將根據選擇的編碼轉化爲相應的byte。
OutputStreamWriter
將字節流根據選擇的編碼轉化爲字符流。
public OutputStreamWriter(OutputStream out, String encoding) throws UnsupportedEncodingException public void write(String s) throws IOException;
Reader
類API
protected Reader() protected Reader(Object lock) public abstract int read(char[] text, int offset, int length) throws IOException //返回unicode對應的0~65535之間的整數 //若是到達了流的末尾,則返回-1 public int read() throws IOException public int read(char[] text) throws IOException //跳過n個字符 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
InputStreamReader
public InputStreamReader(InputStream in) //若是沒有能夠匹配的編碼,則拋出UnsupportedEncodingException public InputStreamReader(InputStream in, String encoding) throws UnsupportedEncodingException
使用InputStreamReader
的範例:
public static String getMacCyrillicString(InputStream in) throws IOException { InputStreamReader r = new InputStreamReader(in, "MacCyrillic"); StringBuilder sb = new StringBuilder(); int c; while ((c = r.read()) != -1) sb.append((char) c); return sb.toString(); }
PrintWriter
PrintStream的字符形式閱讀,儘可能使用PrintWriter而非PrintStream由於正如前面提到的,PrintStream依賴於當前平臺的編碼,而且沒法修改。
public PrintWriter(Writer out) public PrintWriter(Writer out, boolean autoFlush) public PrintWriter(OutputStream out) public PrintWriter(OutputStream out, boolean autoFlush) public void flush() public void close() public boolean checkError() public void write(int c) public void write(char[] text, int offset, int length) public void write(char[] text) public void write(String s, int offset, int length) 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[] 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)
Java Network Prograing 4th edition
HTTP權威指南
想要了解更多開發技術,面試教程以及互聯網公司內推,歡迎關注個人微信公衆號!將會不按期的發放福利哦~