1. 程式人生 > >通俗易懂的NIO講解

通俗易懂的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

M11IO執行緒處理多個客戶端連線)

IO型別(阻塞)

阻塞IO

非阻塞IO

IO型別(同步)

同步IO

同步IOIO多路複用)

API使用難度

簡單

非常複雜

調式難度

簡單

複雜

可靠性

非常差

吞吐量


二、NIO包API的用法。

(1)Channel:Java NIO中的所有I/O操作都基於Channel物件,一個Channel(通道)代表和某一實體的連線,這個實體可以是檔案、網路套接字等。也就是說,通道是Java NIO提供的一座橋樑,用於我們的程式和作業系統底層I/O服務進行互動。

FileChannel:檔案通道,從輸入流輸出流中獲取例項,常見的使用場景就是從一個檔案拷貝其內容到另一個檔案。


    
  1. FileInputStream fis = new FileInputStream( “E://zfb.txt”);
  2. FileChannel ifc = fis.getChannel();
  3. FileOutputStream os = new FileOutputStream( “E://zfb2.txt”);
  4. FileChannel ofc = os.getChannel();
  5. ByteBuffer buffer = ByteBuffer.allocate( 1024);
  6. while (ifc.read(buffer) != - 1){
  7. System.out.println( 1);
  8. buffer.flip();
  9. ofc.write(buffer);
  10. buffer.clear();
  11. }

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


    
  1. public final Buffer flip() { //用於寫模式到讀模式的轉換
  2. limit = position; //設定上屆為當前位置
  3. position = 0; //當前位置設定為0
  4. mark = - 1; //重置寫標記
  5. return this;
  6. }

    
  1. public final Buffer rewind() { //設定當前位置為0
  2. position = 0;
  3. mark = - 1;
  4. return this;
  5. }

    
  1. public final Buffer clear() { //通過指標移動的方式清空緩衝區
  2. position = 0; //設定當前位置為0
  3. limit = capacity; //上限為容量
  4. mark = - 1;
  5. return this;
  6. }

(3)Selector:Selector(選擇器)是一個特殊的元件,用於採集各個通道的狀態(或者說事件)。我們先將通道註冊到選擇器,並設定關心的事件,然後就可以通過呼叫select()方法,監聽關注事件的發生。通道有4個事件可供我們監聽:Accept、Connect、Read、Write。

selector.select()方法是阻塞的方法!!!若註冊的通道沒有事件到達,則不會向下執行。但是NIO的IO機制是非阻塞的!!!

三、NIO的程式設計模型——基於Reactor模式的事件驅動。

NIO是同步非阻塞的,但是提供了基於Selector的非同步網路IO。這句話的意思是NIO的IO機制是同步非阻塞的,而基於Selector這個元件的程式設計模型(Reactor)是事件驅動的,是非同步的。

下面來看一下一般採用NIO的程式設計模型:


demo服務端程式碼:


    
  1. public class NIOServer {
  2. private final static int BUFFER_SIZE = 1024;
  3. private final static int PORT = 52621;
  4. private ServerSocketChannel serverSocketChannel;
  5. private ByteBuffer byteBuffer;
  6. private Selector selector;
  7. public NIOServer(){
  8. try {
  9. init();
  10. } catch (IOException e) {
  11. e.printStackTrace();
  12. }
  13. }
  14. public void init() throws IOException { //初始化
  15. serverSocketChannel = ServerSocketChannel.open();
  16. serverSocketChannel.configureBlocking( false);
  17. serverSocketChannel.bind( new InetSocketAddress(PORT));
  18. selector = Selector.open();
  19. serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
  20. byteBuffer = ByteBuffer.allocateDirect(BUFFER_SIZE);
  21. }
  22. private void listen() throws IOException {
  23. while ( true){
  24. if (selector.select() == 0){
  25. continue;
  26. }
  27. Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
  28. while (iterator.hasNext()){
  29. SelectionKey key = iterator.next();
  30. if (key.isAcceptable()){
  31. ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
  32. SocketChannel socketChannel = ssc.accept();
  33. socketChannel.configureBlocking( false);
  34. socketChannel.register(selector, SelectionKey.OP_READ);
  35. replyClient(socketChannel);
  36. }
  37. if (key.isReadable()){
  38. readDataFromClient(key);
  39. }
  40. iterator.remove();
  41. }
  42. }
  43. }
  44. private void readDataFromClient(SelectionKey key) throws IOException {
  45. SocketChannel socketChannel = (SocketChannel) key.channel();
  46. byteBuffer.clear();
  47. int n;
  48. while ((n = socketChannel.read(byteBuffer)) > 0){
  49. byteBuffer.flip();
  50. while (byteBuffer.hasRemaining()){
  51. socketChannel.write(byteBuffer);
  52. }
  53. byteBuffer.clear();
  54. }
  55. if (n < 0){
  56. socketChannel.close();
  57. }
  58. }
  59. private void replyClient(SocketChannel channel) throws IOException {
  60. byteBuffer.clear();
  61. byteBuffer.put( "Hello,I am server!".getBytes());
  62. byteBuffer.flip();
  63. channel.write(byteBuffer);
  64. }
  65. public static void main(String[] args) {
  66. try {
  67. new NIOServer().listen();
  68. } catch (IOException e) {
  69. e.printStackTrace();
  70. }
  71. }
  72. }

客戶端程式碼:


    
  1. public class NIOClient {
  2. private final static int BUFFER_SIZE = 1024;
  3. private final static int PORT = 52621;
  4. private SocketChannel socketChannel;
  5. private ByteBuffer byteBuffer;
  6. public void connect() throws IOException {
  7. socketChannel = SocketChannel.open();
  8. socketChannel.connect( new InetSocketAddress( "127.0.0.1", PORT));
  9. byteBuffer = ByteBuffer.allocate(BUFFER_SIZE);
  10. receive();
  11. }
  12. private void receive() throws IOException {
  13. while ( true){
  14. int n;
  15. byteBuffer.clear();
  16. while ((n = socketChannel.read(byteBuffer)) > 0){
  17. byteBuffer.flip();
  18. System.out.println( "從伺服器收到訊息: " + new String(byteBuffer.array()));
  19. //socketChannel.write(byteBuffer);//發訊息
  20. byteBuffer.clear();
  21. }
  22. }
  23. }
  24. public static void main(String[] args) {
  25. try {
  26. new NIOClient().connect();
  27. } catch (IOException e) {
  28. e.printStackTrace();
  29. }
  30. }
  31. }

一、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

M11IO執行緒處理多個客戶端連線)

IO型別(阻塞)

阻塞IO

非阻塞IO

IO型別(同步)

同步IO

同步IOIO多路複用)

API使用難度

簡單

非常複雜

調式難度

簡單

複雜

可靠性

非常差

吞吐量


二、NIO包API的用法。

(1)Channel:Java NIO中的所有I/O操作都基於Channel物件,一個Channel(通道)代表和某一實體的連線,這個實體可以是檔案、網路套接字等。也就是說,通道是Java NIO提供的一座橋樑,用於我們的程式和作業系統底層I/O服務進行互動。

FileChannel:檔案通道,從輸入流輸出流中獲取例項,常見的使用場景就是從一個檔案拷貝其內容到另一個檔案。


  
  1. FileInputStream fis = new FileInputStream( “E://zfb.txt”);
  2. FileChannel ifc = fis.getChannel();
  3. FileOutputStream os = new FileOutputStream( “E://zfb2.txt”);
  4. FileChannel ofc = os.getChannel();
  5. ByteBuffer buffer = ByteBuffer.allocate( 1024);
  6. while (ifc.read(buffer) != - 1){
  7. System.out.println( 1);
  8. buffer.flip();
  9. ofc.write(buffer);
  10. buffer.clear();
  11. }

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


  
  1. public final Buffer flip() { //用於寫模式到讀模式的轉換
  2. limit = position; //設定上屆為當前位置
  3. position = 0; //當前位置設定為0
  4. mark = - 1; //重置寫標記
  5. return this;
  6. }

  
  1. public final Buffer rewind() { //設定當前位置為0
  2. position = 0;
  3. mark = - 1;
  4. return this;
  5. }

  
  1. public final Buffer clear() { //通過指標移動的方式清空緩衝區
  2. position = 0; //設定當前位置為0
  3. limit = capacity; //上限為容量
  4. mark = - 1;
  5. return this;
  6. }

(3)Selector:Selector(選擇器)是一個特殊的元件,用於採集各個通道的狀態(或者說事件)。我們先將通道註冊到選擇器,並設定關心的事件,然後就可以通過呼叫select()方法,監聽關注事件的發生。通道有4個事件可供我們監聽:Accept、Connect、Read、Write。

selector.select()方法是阻塞的方法!!!若註冊的通道沒有事件到達,則不會向下執行。但是NIO的IO機制是非阻塞的!!!

三、NIO的程式設計模型——基於Reactor模式的事件驅動。

NIO是同步非阻塞的,但是提供了基於Selector的非同步網路IO。這句話的意思是NIO的IO機制是同步非阻塞的,而基於Selector這個元件的程式設計模型(Reactor)是事件驅動的,是非同步的。

下面來看一下一般採用NIO的程式設計模型: