概述html
java中io輸入輸出流是阻塞的,如BufferedReader的readLine(),InputStrean的read()方法。當程序沒有讀到有效數據,程序將在此處阻塞。比較典型的是,在用socket傳遞數據,若是發送數據的一方,沒有關閉流,接收數據的一方,接收數據後將一直阻塞。面向流的輸入輸出,最底層一次只能處理一個字節,所以效率不高。java
jdk1.4提供了新的IO即NIO。數組
NIO中有三個重要的特性。
安全
Channel,通道。模擬傳統的輸入輸出流,既能夠作輸入又能夠作輸出。
服務器
Buffer,緩衝。本質是一個數組,發送到Channel的全部對象都必須先放到Buffer中。讀取數據也先讀到Buffer。
多線程
Selector。selector容許單線程處理多個Channel,可監控多個服務socket
Buffer簡介
ide
buffer中有三個重要概念:容量(capactiy)、界限(limit)和位置(position,帶讀取數據的下標)。
ui
這些值知足以下關係:0<=mark<=position<=limit<=capacity
spa
Buffer中包含兩個重要的方法flip和clear,flip爲從Buffer中取出數據作準備,clear則向Buffer中裝入數據作準備。
當Buffer中裝入數據結束後,調用flip方法,該方法將limit設置爲position所在的位置,將position設爲0,這樣Buffer中讀數據老是從0開始,作好輸出準備。當Buffer輸出數據結束,Buffer調用clear方法,clear方法不是清空Buffer的數據,它僅僅將poition置爲0,將limit置爲capacity,這樣爲再次向Buffer裝入數據作準備,此時原先的數據沒有清除,新寫的數據會替換原先的數據。
Channel簡介
全部的Channel都不該該經過構造器來直接建立,而是經過InputStream、OutputStream的getChannel方法來返回對應的Channel。
Channel中最經常使用的三類方法是map、read和write,其中map方法用於將Channel對應的部分或所有數據映射爲ByteBuffer;而read或write方法用於向Buffer讀取寫入數據。
static void channelTest() throws Exception{ FileInputStream is = new FileInputStream(new File("G:/百無聊賴.txt")); FileOutputStream out = new FileOutputStream(new File("G:/ttt.txt")); FileChannel readChannel = is.getChannel(); FileChannel writechannel = out.getChannel(); ByteBuffer buffer = ByteBuffer.allocate(1024); while(readChannel.read(buffer)!=-1){ buffer.flip();//準備取出數據 writechannel.write(buffer); buffer.clear();//準備裝入數據 } writechannel.close(); readChannel.close(); out.close(); is.close(); }
由於jdk對以前的IO採用nio的機制從新實現了,因此直接使用nio對數據進行讀寫並不經常使用。使用channel的map方法,進行內存映射,能夠快速的讀寫數據,可是這種方式不安全,僅使用與讀數據。NIO最經常使用的功能應該是它的Selector。
Java NIO的選擇器容許一個單獨的線程來監視多個輸入通道,你能夠註冊多個通道使用一個選擇器,而後使用一個單獨的線程來「選擇」通道:這些通道里已經有能夠處理的輸入,或者選擇已準備寫入的通道。這種選擇機制,使得一個單獨的線程很容易來管理多個通道。
以下兩篇博文對NIO包括selector的介紹都不錯
http://www.iteye.com/magazines/132-Java-NIO#579
http://www.xuebuyuan.com/1600515.html
下面構建一個小例子對比下NIO和IO服務器
服務端代碼:
package com.base.nio; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.nio.charset.Charset; public class Server { public static void main(String[] args) throws Exception{ // nioServer(); socketServer(); } public static void nioServer() throws Exception{ int port=9999; Selector selector=Selector.open(); ServerSocketChannel ssc=ServerSocketChannel.open(); ServerSocket serverSocket=ssc.socket(); serverSocket.bind(new InetSocketAddress(port)); System.out.println("Server listen on port: "+port); ssc.configureBlocking(false); ssc.register(selector, SelectionKey.OP_ACCEPT); while(true){ int nKeys=selector.select(); if(nKeys>0){ for (SelectionKey key : selector.selectedKeys()) { if(key.isAcceptable()){ ServerSocketChannel server=(ServerSocketChannel) key.channel(); SocketChannel sc=server.accept(); if(sc==null){ continue; } sc.configureBlocking(false); sc.register(selector, SelectionKey.OP_READ); } else if(key.isReadable()){ ByteBuffer buffer=ByteBuffer.allocate(1024); SocketChannel sc=(SocketChannel) key.channel(); int readBytes=0; readBytes = sc.read(buffer);//若是客戶端傳來的數據長度超過1024,會丟失報文。 buffer.flip(); if(readBytes>0){ String message =Charset.forName("UTF-8").decode(buffer).toString(); System.out.println("Message from client: "+ message); if("quit".equalsIgnoreCase(message.trim())){ sc.close(); selector.close(); System.out.println("Server has been shutdown!"); System.exit(0); } String outMessage="Server response:"+message; sc.write(Charset.forName("UTF-8").encode(outMessage)); } } } selector.selectedKeys().clear(); } } } public static void socketServer() throws IOException, InterruptedException, Exception { ServerSocket ss = new ServerSocket(9999); while(true){ Socket socket = ss.accept(); BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())); String getMsg = br.readLine(); if("quit".equalsIgnoreCase(getMsg.trim())){ ss.close(); System.out.println("Server has been shutdown!"); System.exit(0); } System.out.println(getMsg); BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); bw.write("Hello Client"); bw.newLine(); bw.flush(); Thread.sleep(10000); Client.close(socket, null, bw, br); } } }
客戶端代碼:
package com.base.nio; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.net.Socket; public class Client implements Runnable{ public static void main(String[] args) throws Exception{ for(int i=0;i<10;i++){ Client c = new Client(); Thread thread = new Thread(c); thread.start(); } } @Override public void run() { Socket socket = null; OutputStream out = null; BufferedWriter bw = null; BufferedReader br = null; try { socket = new Socket("127.0.0.1", 9999); out = socket.getOutputStream(); bw = new BufferedWriter(new OutputStreamWriter(out)); bw.write("Hello Server"); bw.newLine(); bw.flush(); Thread.sleep(10000); br = new BufferedReader(new InputStreamReader(socket.getInputStream())); String retMsg = br.readLine(); System.out.println(retMsg+Thread.currentThread().getName()); } catch (Exception e) { e.printStackTrace(); } finally{ try { Client.close(socket, out, bw,br); } catch (Exception e) { e.printStackTrace(); } } } public static void close(Socket socket, OutputStream out, BufferedWriter bw,BufferedReader br) throws Exception{ if(br!=null) br.close(); if(bw!=null) bw.close(); if(out!=null) out.close(); if(socket!=null) socket.close(); } }
經過運行分析,發現nio是非阻塞的,不用等每個客戶端的鏈接請求完成,就能夠進行下一個鏈接。而傳統的socket會等待客戶端的每一個請求完成,纔去鏈接下個客戶端請求。
nio是經過while(true)進行輪詢,來不斷監聽客戶端的請求狀態。而socket是經過阻塞的方式來等待客戶端的請求狀態。
感受NIO最大的優點就在於它的非阻塞,減小了等待客戶的響應。若是使用socket要想達到一樣的效果,必須使用線程池,使用線程池就相對耗資源些。
至於監聽多個chanel,也就是同時監聽多個端口。好比以上例子,9999用於監聽客戶端會話。若是服務端還要一個8888端口,來控制其餘服務,好比關閉服務等等。傳統的socket也必須用多線程來實現。
可是感受NIO採用這種輪的方式,不是很優雅。可能我瞭解的很少吧。