1. 程式人生 > >Java NIO的理解和應用

Java NIO的理解和應用

> Java NIO是一種基於通道和緩衝區的I/O方式,已經被廣泛的應用,成為解決高併發與大量連線和I/O處理問題的有效方式。 ## Java NIO相關元件 Java NIO主要有三個核心部分組成,分別是:Channel(通道),Buffer(緩衝區), Selector(選擇器) + Channel `Channel`是所有訪問IO裝置的統稱。型別與IO中的Stream,而通道是雙向的,既可以讀又可以寫,但是Stream是單項的。常用的通道有:`SocketChannel`和`ServerSocketChannel`(對應TCP的客戶端和伺服器端)、`FileChannel`(對應檔案IO)、`DatagramChannel`(對應UDP)等 + Buffer 所有資料的讀寫都要經過`Buffer`,`Buffer`直接和`Channel`打交道,是一個儲存資料的容器。通過呼叫`Channel.write`方法將資料寫入`Buffer`,`Channel.read`方法將資料從`Buffer`中讀取出來。常用的`Buffer`有:`ByteBuffer`、`LongBuffer`、`IntBuffer`、`StringCharBuffer`等 + Selector `Selector`用來監聽多個`Channel`的事件(比如:Read、Write、Connect和Accept等),通過單個執行緒輪詢的方式實現了對多個`Channel`的監聽。 ## Java IO與NIO的區別 NIO是一種叫非阻塞IO(Non-blocking I/O),基於I/O多路複用來實現的(可參考:[I/O模型](https://www.cnblogs.com/pinxiong/p/13288134.html)、[select、poll和epoll之間的區別](https://www.cnblogs.com/pinxiong/p/13288140.html))。NIO與之前傳統的I/O模型有很大的不同,具體表現在以下幾個方面: + 面向流與面向緩衝 Java IO和NIO之間一個最大的區別是,IO是面向流的,NIO是面向緩衝區的。Java IO每次從資料流中讀一個或多個位元組,直至讀取所有位元組,資料流是一次性的,讀取完以後,不能前後移動流中的資料。Java NIO是將資料讀取到緩衝區,可以通過`position`來回移動訪問緩衝區中的資料。 + 阻塞與非阻塞IO Java IO中呼叫`read`和`write`方法的執行緒會被阻塞的,直到資料全部讀入或者全部寫入完為止。而在Java NIO中,如果需要讀寫資料只用和緩衝區打交道,將資料從緩衝區讀取或者寫入緩衝區以後,執行緒可以繼續做其他事情,不會被block住。 + 選擇器(Selector) `Selector`是基於I/O多路複用的機制實現的,將多個`Channel`註冊到一個`Selector`上,`Selector`通過輪詢監聽所有註冊的通道上是否有`SelectionKey`發生,如果發生了,然後將`SelectionKey`分派給其他執行緒處理。 ## Java NIO的應用 通過Java NIO技術簡單實現了一個服務端與客戶端通訊的case,具體功能如下: + 服務端可以向客戶端廣播訊息 + 服務端將一個客戶端的訊息轉發給其他客戶端 + 客戶端向服務端傳送訊息 + 客戶端接收服務端的訊息 服務端程式碼如下: ``` import java.io.IOException; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; import java.nio.ByteBuffer; import java.nio.channels.*; import java.nio.charset.Charset; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Scanner; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Server { public static void main(String[] args) throws IOException { new Server().start(); // 啟動服務端程式 } public Server() throws IOException { this.init(); // 初始化服務端資料 } /** * 服務端埠 */ private int port = 9999; /** * 服務端的Selector用來監聽Channel的事件. */ private Selector selector; /** * 字元資料編碼 */ private Charset charset = Charset.forName("UTF-8"); /** * 讀快取,分配1024Byte的空間 */ private ByteBuffer readBuffer = ByteBuffer.allocate(1024); /** * 寫快取,分配1024Byte的空間 */ private ByteBuffer writeBuffer = ByteBuffer.allocate(1024); /** * 儲存所有客戶端的Channel,轉發的時候使用 */ pri