I/O指的是計算機與外部世界,或者程序與計算機其餘部分的接口,即輸入/輸出。緩存
在JAVA中,一般都以流的方式完成I/O,經過一個Stream對象操做。這種操做方法是堵塞的,沒法移動讀取位置的(只能一直往下讀,不能後退),而且效率較低。JAVA爲了提升I/O效率,在1.4以後,推出了NIO。服務器
I/O | NIO |
面向流,一次讀取一個或多個字節,在流中沒法先後移動 | 面向緩存,讀取的數據先統一放在緩存中,在緩存中能先後移動 |
堵塞,從流讀取數據時,線程沒法作其餘事情 | 非堵塞,數據還未完整讀取到緩存中時,線程能夠先作其餘事 |
一對一:一條線程負責一個數據操做任務 | 一對多,一條線程負責多個數據操做任務 |
NIO要實現面向緩存的非堵塞數據讀取,依賴"Channel(通道)"和"Buffer(緩衝區)";網絡
NIO要實現一條線程管理多個數據操做,依賴"Selector(選擇器)"。app
A channel represents an open connection to an entity such as a hardware device, a file, a network socket, or a program component that is capable of performing one or more distinct I/O operations, for example reading or writing.異步
即經過Channel,你能夠和外部進行數據的讀寫。socket
雖然NIO的Channel和I/O的stream很像,不過仍是有區別的:ide
根據Channel數據來源不一樣,Channel有不一樣的實現:性能
I/O 和 NIO 數據操做對比spa
// I/O流操做 FileInputStream inputStream = new FileInputStream("io.txt"); byte[] data = new byte[1024]; while(inputStream.read(data) != -1){ // 從流中讀取多個字節,進行操做 }
// NIO channel操做 // 從文件流中獲取channel FileInputStream inputStream = new FileInputStream("nio.txt"); FileChannel fileChannel = inputStream.getChannel(); // 新建緩存,用於存放Channel讀取的數據 ByteBuffer buffer = ByteBuffer.allocate(1024); // 經過Channel讀取數據到緩存中 fileChannel.read(buffer);
對於FileChannel之間,利用transferFrom和transferTo能夠直接進行數據傳輸,提升性能。線程
將fromChannel中數據傳輸到toChannel 中,position指toChannel中開始的位置,count指接收的數據:
toChannel.transferFrom(fromChannel, position, count) 或
fromChannel.transferTo(position, count, toChannel)
A container for data of a specific primitive type.
A buffer is a linear, finite sequence of elements of a specific primitive type. Aside from its content, the essential properties of a buffer are its capacity, limit, and position.
buffer緩衝區是一塊內存,用於存放原始類型數據。它的特色是:線性,有限,有序,只能存一種原始類型數據。
根據buffer中存放的原始類型不一樣,有如下幾種Buffer實現
A multiplexor of SelectableChannel objects.
一個Selector管理多個可管理的Channel通道,它主要用於檢查它負責的Channel通道的狀態。
以下是一個線程經過使用selector管理多個Channel通道的示意圖:
使用Selector管理多個Channel通道的過程以下
Selector是由SelectorProvider建立出來的。具體有兩種方式建立:
Selector要管理Channel,必須先將Channel註冊到Selector上。只有SelectableChannel類才能用Selector管理。註冊的方法有兩個:
SelectionKey用於記錄Channel通道與Selector之間的關係。它有如下幾個重要的方法
boolean isInterestedInAccept = interestSet & SelectionKey.OP_ACCEPT;
先調用select方法獲取處於就緒狀態的Channel數量,當數量大於0時,再調用selectedKeys方法獲取處於就緒狀態的SelectionKey集合。
select方法有如下三個:
Set<SelectionKey> selectedKeys() : 返回SelectionKey集合。
因select()而block的線程,另一個線程能夠調用該selector的wakeup()來喚醒。
selector使用完後,須要調用close()方法關閉。
Selector selector = Selector.open(); channel.configureBlocking(false); SelectionKey key = channel.register(selector, SelectionKey.OP_READ); while(true) { int readyChannels = selector.select(); if(readyChannels == 0) continue; Set<SelectionKey> selectedKeys = selector.selectedKeys(); Iterator<SelectionKey> keyIterator = selectedKeys.iterator(); while(keyIterator.hasNext()) { SelectionKey key = keyIterator.next(); if(key.isAcceptable()) { // a connection was accepted by a ServerSocketChannel. } else if (key.isConnectable()) { // a connection was established with a remote server. } else if (key.isReadable()) { // a channel is ready for reading } else if (key.isWritable()) { // a channel is ready for writing } keyIterator.remove(); } }
NIO容許咱們只用一條線程來管理多個通道(網絡鏈接或文件),隨之而來的代價是解析數據相對於阻塞流來講可能會變得更加的複雜。
若是你須要同時管理成千上萬的連接,這些連接只發送少許數據,例如聊天服務器,用NIO來實現這個服務器是有優點的。或者,若是你須要維持大量的連接,例如P2P網絡,用單線程來管理這些 連接也是有優點的。這種單線程多鏈接的設計能夠用下圖描述:
若是連接數不是不少,可是每一個連接的佔用較大帶寬,每次都要發送大量數據,那麼使用傳統的IO設計服務器多是最好的選擇。下面是經典IO服務設計圖: