Java NIO的理解和應用
阿新 • • 發佈:2020-07-22
> 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