NIO(阻塞IO),不論是磁盤IO仍是網絡IO,數據在寫入OutputStream或從InputStream讀取的時候均可能發生阻塞,當發生阻塞,線程會失去CPU的使用權,這個在當前訪問量大和有性能要求的狀況下是不被容許的。若果咱們採用一個客戶端對應一個線程的方式來解決這個問題,可是當須要大量的HTTP長鏈接的時候就會出現問題,好比淘寶的Web旺旺。爲了解決上面的問題,咱們來介紹NIO。
IO NIO java
面向流 面向緩衝 算法
阻塞IO 非阻塞IO 服務器
無 選擇器
網絡
首先引用iteye一個大神的回覆關於NIO的總結:併發
Channel 通道 Buffer 緩衝區 Selector是NIO的三個核心部分。 dom
選擇器其中Channel對應之前的流,Buffer不是什麼新東西,Selector是由於nio可使用異步的非堵塞模式才加入的東西。 之前的流老是堵塞的,一個線程只要對它進行操做,其它操做就會被堵塞,也就至關於水管沒有閥門,你伸手接水的時候,無論水到了沒有,你就都只能耗在接水(流)上。異步
nio的Channel的加入,至關於增長了水龍頭(有閥門),雖然一個時刻也只能接一個水管的水,但依賴輪換策略,在水量不大的時候,各個水管裏流出來的水,均可以獲得妥善接納,這個關鍵之處就是增長了一個接水工,也就Selector,他負責協調,也就是看哪根水管有水了的話,在當前水管的水接到必定程度的時候,就切換一下:臨時關上當前水龍頭,試着打開另外一個水龍頭(看看有沒有水)。socket
當其餘人須要用水的時候,不是直接去接水,而是事前提了一個水桶給接水工,這個水桶就是Buffer。也就是,其餘人雖然也可能要等,但不會在現場等,而是回家等,能夠作其它事去,水接滿了,接水工會通知他們。性能
其實也是很是接近當前社會分工細化的現實,也是統分利用現有資源達到併發效果的一種很經濟的手段,而不是動不動就來個串行處理,雖然那樣是最簡單的,但也是最浪費資源的方式。測試
NIO 有一個主要的類Selector,這個相似一個觀察者,只要咱們把須要探知的socketchannel告訴Selector,咱們接着作別的事情,當有事件發生時,他會通知咱們,傳回一組SelectionKey,咱們讀取這些Key,就會得到咱們剛剛註冊過的socketchannel,而後,咱們從這個Channel中讀取數據,放心,包準可以讀到,接着咱們能夠處理這些數據。 Selector內部原理實際是在作一個對所註冊的channel的輪詢訪問,不斷的輪詢(目前就這一個算法),一旦輪詢到一個channel有所註冊的事情發生,好比數據來了,他就會站起來報告,交出一把鑰匙,讓咱們經過這把鑰匙來讀取這個channel的內容。
RandomAccessFile aFile = new RandomAccessFile("/Users/wjk/myproject/test/IO/src/main/resources/io.txt", "rw"); //獲取接水工 FileChannel channel = aFile.getChannel(); //設置水桶的大小 ByteBuffer buffer = ByteBuffer.allocate(43); //節水工用水桶接水 int bytesRead = channel.read(buffer); if (bytesRead != -1) { System.out.println("read: " + bytesRead); //接水工準備倒出水桶裏的水(能夠倒出剛纔接的水) buffer.flip(); //水桶是否還有水 while (buffer.hasRemaining()) { System.out.println((char) buffer.get()); } //倒掉剩餘的水 //compact(將剩餘的水留在水桶中) buffer.clear(); bytesRead = channel.read(buffer); } aFile.close();
/** * 服務器端 **/ public class NIOServer { //通道管理器 private Selector selector; /** * 得到一個ServerSocket通道,並對該通道作一些初始化的工做 * * @param port 綁定的端口號 * @throws IOException */ public void initServer(int port) throws IOException { // 得到一個ServerSocket通道 ServerSocketChannel serverChannel = ServerSocketChannel.open(); // 設置通道爲非阻塞 serverChannel.configureBlocking(false); // 將該通道對應的ServerSocket綁定到port端口 serverChannel.socket().bind(new InetSocketAddress(port)); // 得到一個通道管理器 this.selector = Selector.open(); //將通道管理器和該通道綁定,併爲該通道註冊SelectionKey.OP_ACCEPT事件,註冊該事件後, //當該事件到達時,selector.select()會返回,若是該事件沒到達selector.select()會一直阻塞。 serverChannel.register(selector, SelectionKey.OP_ACCEPT); } /** * 採用輪詢的方式監聽selector上是否有須要處理的事件,若是有,則進行處理 * * @throws IOException */ @SuppressWarnings("unchecked") public void listen() throws IOException { System.out.println("服務端啓動成功!"); // 輪詢訪問selector while (true) { //當註冊的事件到達時,方法返回;不然,該方法會一直阻塞 selector.select(); // 得到selector中選中的項的迭代器,選中的項爲註冊的事件 Iterator ite = this.selector.selectedKeys().iterator(); while (ite.hasNext()) { SelectionKey key = (SelectionKey) ite.next(); // 刪除已選的key,以防重複處理 ite.remove(); // 客戶端請求鏈接事件 if (key.isAcceptable()) { ServerSocketChannel server = (ServerSocketChannel) key .channel(); // 得到和客戶端鏈接的通道 SocketChannel channel = server.accept(); // 設置成非阻塞 channel.configureBlocking(false); //在這裏能夠給客戶端發送信息哦 channel.write(ByteBuffer.wrap(new String("向客戶端發送了一條信息").getBytes())); //在和客戶端鏈接成功以後,爲了能夠接收到客戶端的信息,須要給通道設置讀的權限。 channel.register(this.selector, SelectionKey.OP_READ); // 得到了可讀的事件 } else if (key.isReadable()) { read(key); } } } } /** * 處理讀取客戶端發來的信息 的事件 * * @param key * @throws IOException */ public void read(SelectionKey key) throws IOException { // 服務器可讀取消息:獲得事件發生的Socket通道 SocketChannel channel = (SocketChannel) key.channel(); // 建立讀取的緩衝區 ByteBuffer buffer = ByteBuffer.allocate(1024); channel.read(buffer); byte[] data = buffer.array(); String msg = new String(data).trim(); System.out.println("服務端收到信息:" + msg); ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes()); channel.write(outBuffer);// 將消息回送給客戶端 } /** * 啓動服務端測試 * * @throws IOException */ public static void main(String[] args) throws IOException { NIOServer server = new NIOServer(); server.initServer(8001); server.listen(); } } /** * 客戶端 **/ public class NIOClient { //通道管理器 private Selector selector; /** * 得到一個Socket通道,並對該通道作一些初始化的工做 * @param ip 鏈接的服務器的ip * @param port 鏈接的服務器的端口號 * @throws IOException */ public void initClient(String ip,int port) throws IOException { // 得到一個Socket通道 SocketChannel channel = SocketChannel.open(); // 設置通道爲非阻塞 channel.configureBlocking(false); // 得到一個通道管理器 this.selector = Selector.open(); // 客戶端鏈接服務器,其實方法執行並無實現鏈接,須要在listen()方法中調 //用channel.finishConnect();才能完成鏈接 channel.connect(new InetSocketAddress(ip,port)); //將通道管理器和該通道綁定,併爲該通道註冊SelectionKey.OP_CONNECT事件。 channel.register(selector, SelectionKey.OP_CONNECT); } /** * 採用輪詢的方式監聽selector上是否有須要處理的事件,若是有,則進行處理 * @throws IOException */ @SuppressWarnings("unchecked") public void listen() throws IOException { // 輪詢訪問selector while (true) { selector.select(); // 得到selector中選中的項的迭代器 Iterator ite = this.selector.selectedKeys().iterator(); while (ite.hasNext()) { SelectionKey key = (SelectionKey) ite.next(); // 刪除已選的key,以防重複處理 ite.remove(); // 鏈接事件發生 if (key.isConnectable()) { SocketChannel channel = (SocketChannel) key .channel(); // 若是正在鏈接,則完成鏈接 if(channel.isConnectionPending()){ channel.finishConnect(); } // 設置成非阻塞 channel.configureBlocking(false); //在這裏能夠給服務端發送信息哦 channel.write(ByteBuffer.wrap(new String("向服務端發送了一條信息").getBytes())); //在和服務端鏈接成功以後,爲了能夠接收到服務端的信息,須要給通道設置讀的權限。 channel.register(this.selector, SelectionKey.OP_READ); // 得到了可讀的事件 } else if (key.isReadable()) { read(key); } } } } /** * 處理讀取服務端發來的信息 的事件 * @param key * @throws IOException */ public void read(SelectionKey key) throws IOException{ //和服務端的read方法同樣 } /** * 啓動客戶端測試 * @throws IOException */ public static void main(String[] args) throws IOException { NIOClient client = new NIOClient(); client.initClient("localhost",8001); client.listen(); } }