通俗易懂的NIO講解
一、NIO是什麼?
NIO的全稱是New I/O,與之相對應的是Java中傳統的I/O,這裡都指的是Java的API包。
傳統的IO包提供的是同步阻塞IO,即當用戶執行緒發出IO請求後,核心會去檢視資料是否已經就緒,若未就緒,則使用者執行緒會處於阻塞狀態(讓出CPU),當資料就緒後,核心會將資料複製到使用者執行緒,並把結果返回給使用者執行緒,同時接觸使用者執行緒的阻塞,同步體現在使用者執行緒需要等待資料就緒後才能向後執行(後面的執行依賴於前面的結果)。伺服器實現模式為一個連線一個執行緒,即客戶端有連線請求時伺服器端就需要啟動一個執行緒進行處理,如果這個連線不做任何事情會造成不
而NIO包提供的IO是同步非阻塞IO,非阻塞體現在使用者執行緒發起IO請求後,會直接得到返回結果,即便在資料未就緒的情況下,也能馬上得到失敗資訊。而同步體現在使用者執行緒需要主動去輪詢直到發現數據就緒,再主動將資料從核心拷貝到使用者執行緒。伺服器實現模式為多個連線一個執行緒(IO多路複用),即客戶端傳送的連線請求都會註冊到多路複用器上,多路複用器輪詢到連線有I/O請求時才啟動一個執行緒進行處理。
傳統IO模型 |
NIO |
|
客戶端個數IO執行緒 |
1:1 |
M:1(1個IO執行緒處理多個客戶端連線) |
IO型別(阻塞) |
阻塞IO |
非阻塞IO |
IO型別(同步) |
同步IO |
同步IO(IO多路複用) |
API使用難度 |
簡單 |
非常複雜 |
調式難度 |
簡單 |
複雜 |
可靠性 |
非常差 |
高 |
吞吐量 |
低 |
高 |
二、NIO包API的用法。
(1)Channel:Java NIO中的所有I/O操作都基於Channel物件,一個Channel(通道)代表和某一實體的連線,這個實體可以是檔案、網路套接字等。也就是說,通道是Java NIO提供的一座橋樑,用於我們的程式和作業系統底層I/O服務進行互動。
FileChannel:檔案通道,從輸入流輸出流中獲取例項,常見的使用場景就是從一個檔案拷貝其內容到另一個檔案。
-
FileInputStream fis =
new FileInputStream(
“E://zfb.txt”);
-
FileChannel ifc = fis.getChannel();
-
FileOutputStream os =
new FileOutputStream(
“E://zfb2.txt”);
-
FileChannel ofc = os.getChannel();
-
ByteBuffer buffer = ByteBuffer.allocate(
1024);
-
while (ifc.read(buffer) != -
1){
-
System.out.println(
1);
-
buffer.flip();
-
ofc.write(buffer);
-
buffer.clear();
-
}
SocketChannel/ServerSocketChannel:套接字通道,通過靜態方法獲取例項,使用場景在最後會給出demo。
SocketChannel socketChannel = SocketChannel.open();
(2)Buffer:NIO中所使用的緩衝區不是一個簡單的byte陣列,而是封裝過的Buffer類,通過它提供的API,我們可以靈活的操縱資料。與Java基本型別相對應,NIO提供了多種 Buffer 型別,如ByteBuffer、CharBuffer、IntBuffer等,區別就是讀寫緩衝區時的單位長度不一樣(以對應型別的變數為單位進行讀寫)。Buffer中有3個很重要的變數,它們是理解Buffer工作機制的關鍵,分別是capacity (總容量)、position (指標當前位置)、limit (讀/寫邊界位置)。
接下來看三個方法flip、rewind、clear
-
public final Buffer flip() {
//用於寫模式到讀模式的轉換
-
limit = position;
//設定上屆為當前位置
-
position =
0;
//當前位置設定為0
-
mark = -
1;
//重置寫標記
-
return
this;
-
}
-
public final Buffer rewind() {
//設定當前位置為0
-
position =
0;
-
mark = -
1;
-
return
this;
-
}
-
public final Buffer clear() {
//通過指標移動的方式清空緩衝區
-
position =
0;
//設定當前位置為0
-
limit = capacity;
//上限為容量
-
mark = -
1;
-
return
this;
-
}
(3)Selector:Selector(選擇器)是一個特殊的元件,用於採集各個通道的狀態(或者說事件)。我們先將通道註冊到選擇器,並設定關心的事件,然後就可以通過呼叫select()方法,監聽關注事件的發生。通道有4個事件可供我們監聽:Accept、Connect、Read、Write。
selector.select()方法是阻塞的方法!!!若註冊的通道沒有事件到達,則不會向下執行。但是NIO的IO機制是非阻塞的!!!
三、NIO的程式設計模型——基於Reactor模式的事件驅動。
NIO是同步非阻塞的,但是提供了基於Selector的非同步網路IO。這句話的意思是NIO的IO機制是同步非阻塞的,而基於Selector這個元件的程式設計模型(Reactor)是事件驅動的,是非同步的。
下面來看一下一般採用NIO的程式設計模型:
demo服務端程式碼:
-
public
class NIOServer {
-
-
private
final
static
int BUFFER_SIZE =
1024;
-
-
private
final
static
int PORT =
52621;
-
-
private ServerSocketChannel serverSocketChannel;
-
-
private ByteBuffer byteBuffer;
-
-
private Selector selector;
-
-
public NIOServer(){
-
try {
-
init();
-
}
catch (IOException e) {
-
e.printStackTrace();
-
}
-
}
-
-
public void init() throws IOException {
//初始化
-
serverSocketChannel = ServerSocketChannel.open();
-
serverSocketChannel.configureBlocking(
false);
-
serverSocketChannel.bind(
new InetSocketAddress(PORT));
-
selector = Selector.open();
-
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
-
byteBuffer = ByteBuffer.allocateDirect(BUFFER_SIZE);
-
-
}
-
-
private void listen() throws IOException {
-
while (
true){
-
if (selector.select() ==
0){
-
continue;
-
}
-
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
-
while (iterator.hasNext()){
-
SelectionKey key = iterator.next();
-
if (key.isAcceptable()){
-
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
-
SocketChannel socketChannel = ssc.accept();
-
socketChannel.configureBlocking(
false);
-
socketChannel.register(selector, SelectionKey.OP_READ);
-
replyClient(socketChannel);
-
}
-
if (key.isReadable()){
-
readDataFromClient(key);
-
}
-
iterator.remove();
-
}
-
}
-
}
-
-
private void readDataFromClient(SelectionKey key) throws IOException {
-
SocketChannel socketChannel = (SocketChannel) key.channel();
-
byteBuffer.clear();
-
int n;
-
while ((n = socketChannel.read(byteBuffer)) >
0){
-
byteBuffer.flip();
-
while (byteBuffer.hasRemaining()){
-
socketChannel.write(byteBuffer);
-
}
-
byteBuffer.clear();
-
}
-
if (n <
0){
-
socketChannel.close();
-
}
-
}
-
-
private void replyClient(SocketChannel channel) throws IOException {
-
byteBuffer.clear();
-
byteBuffer.put(
"Hello,I am server!".getBytes());
-
byteBuffer.flip();
-
channel.write(byteBuffer);
-
}
-
-
public static void main(String[] args) {
-
try {
-
new NIOServer().listen();
-
}
catch (IOException e) {
-
e.printStackTrace();
-
}
-
}
-
}
客戶端程式碼:
-
public
class NIOClient {
-
-
private
final
static
int BUFFER_SIZE =
1024;
-
-
private
final
static
int PORT =
52621;
-
-
private SocketChannel socketChannel;
-
-
private ByteBuffer byteBuffer;
-
-
public void connect() throws IOException {
-
socketChannel = SocketChannel.open();
-
socketChannel.connect(
new InetSocketAddress(
"127.0.0.1", PORT));
-
byteBuffer = ByteBuffer.allocate(BUFFER_SIZE);
-
receive();
-
}
-
-
private void receive() throws IOException {
-
while (
true){
-
int n;
-
byteBuffer.clear();
-
while ((n = socketChannel.read(byteBuffer)) >
0){
-
byteBuffer.flip();
-
System.out.println(
"從伺服器收到訊息: " +
new String(byteBuffer.array()));
-
//socketChannel.write(byteBuffer);//發訊息
-
byteBuffer.clear();
-
}
-
}
-
}
-
-
public static void main(String[] args) {
-
try {
-
new NIOClient().connect();
-
}
catch (IOException e) {
-
e.printStackTrace();
-
}
-
}
-
}
一、NIO是什麼?
NIO的全稱是New I/O,與之相對應的是Java中傳統的I/O,這裡都指的是Java的API包。
傳統的IO包提供的是同步阻塞IO,即當用戶執行緒發出IO請求後,核心會去檢視資料是否已經就緒,若未就緒,則使用者執行緒會處於阻塞狀態(讓出CPU),當資料就緒後,核心會將資料複製到使用者執行緒,並把結果返回給使用者執行緒,同時接觸使用者執行緒的阻塞,同步體現在使用者執行緒需要等待資料就緒後才能向後執行(後面的執行依賴於前面的結果)。伺服器實現模式為一個連線一個執行緒,即客戶端有連線請求時伺服器端就需要啟動一個執行緒進行處理,如果這個連線不做任何事情會造成不必要的執行緒開銷,執行緒數量也會受到。
而NIO包提供的IO是同步非阻塞IO,非阻塞體現在使用者執行緒發起IO請求後,會直接得到返回結果,即便在資料未就緒的情況下,也能馬上得到失敗資訊。而同步體現在使用者執行緒需要主動去輪詢直到發現數據就緒,再主動將資料從核心拷貝到使用者執行緒。伺服器實現模式為多個連線一個執行緒(IO多路複用),即客戶端傳送的連線請求都會註冊到多路複用器上,多路複用器輪詢到連線有I/O請求時才啟動一個執行緒進行處理。
傳統IO模型 |
NIO |
|
客戶端個數IO執行緒 |
1:1 |
M:1(1個IO執行緒處理多個客戶端連線) |
IO型別(阻塞) |
阻塞IO |
非阻塞IO |
IO型別(同步) |
同步IO |
同步IO(IO多路複用) |
API使用難度 |
簡單 |
非常複雜 |
調式難度 |
簡單 |
複雜 |
可靠性 |
非常差 |
高 |
吞吐量 |
低 |
高 |
二、NIO包API的用法。
(1)Channel:Java NIO中的所有I/O操作都基於Channel物件,一個Channel(通道)代表和某一實體的連線,這個實體可以是檔案、網路套接字等。也就是說,通道是Java NIO提供的一座橋樑,用於我們的程式和作業系統底層I/O服務進行互動。
FileChannel:檔案通道,從輸入流輸出流中獲取例項,常見的使用場景就是從一個檔案拷貝其內容到另一個檔案。
-
FileInputStream fis =
new FileInputStream(
“E://zfb.txt”);
-
FileChannel ifc = fis.getChannel();
-
FileOutputStream os =
new FileOutputStream(
“E://zfb2.txt”);
-
FileChannel ofc = os.getChannel();
-
ByteBuffer buffer = ByteBuffer.allocate(
1024);
-
while (ifc.read(buffer) != -
1){
-
System.out.println(
1);
-
buffer.flip();
-
ofc.write(buffer);
-
buffer.clear();
-
}
SocketChannel/ServerSocketChannel:套接字通道,通過靜態方法獲取例項,使用場景在最後會給出demo。
SocketChannel socketChannel = SocketChannel.open();
(2)Buffer:NIO中所使用的緩衝區不是一個簡單的byte陣列,而是封裝過的Buffer類,通過它提供的API,我們可以靈活的操縱資料。與Java基本型別相對應,NIO提供了多種 Buffer 型別,如ByteBuffer、CharBuffer、IntBuffer等,區別就是讀寫緩衝區時的單位長度不一樣(以對應型別的變數為單位進行讀寫)。Buffer中有3個很重要的變數,它們是理解Buffer工作機制的關鍵,分別是capacity (總容量)、position (指標當前位置)、limit (讀/寫邊界位置)。
接下來看三個方法flip、rewind、clear
-
public final Buffer flip() {
//用於寫模式到讀模式的轉換
-
limit = position;
//設定上屆為當前位置
-
position =
0;
//當前位置設定為0
-
mark = -
1;
//重置寫標記
-
return
this;
-
}
-
public final Buffer rewind() {
//設定當前位置為0
-
position =
0;
-
mark = -
1;
-
return
this;
-
}
-
public final Buffer clear() {
//通過指標移動的方式清空緩衝區
-
position =
0;
//設定當前位置為0
-
limit = capacity;
//上限為容量
-
mark = -
1;
-
return
this;
-
}
(3)Selector:Selector(選擇器)是一個特殊的元件,用於採集各個通道的狀態(或者說事件)。我們先將通道註冊到選擇器,並設定關心的事件,然後就可以通過呼叫select()方法,監聽關注事件的發生。通道有4個事件可供我們監聽:Accept、Connect、Read、Write。
selector.select()方法是阻塞的方法!!!若註冊的通道沒有事件到達,則不會向下執行。但是NIO的IO機制是非阻塞的!!!
三、NIO的程式設計模型——基於Reactor模式的事件驅動。
NIO是同步非阻塞的,但是提供了基於Selector的非同步網路IO。這句話的意思是NIO的IO機制是同步非阻塞的,而基於Selector這個元件的程式設計模型(Reactor)是事件驅動的,是非同步的。
下面來看一下一般採用NIO的程式設計模型: