1. 程式人生 > >java中NIO的介紹

java中NIO的介紹

pen 內存 rand 提高 lec 高效 由於 ioe 127.0.0.1

  • NIO和IO的區別
  • NIO : new IO
    有的文章說,NIO用到的是塊,也就是每次讀入較多的數據緩存,因此使用效率比IO要高些。
    IO:面向流,阻塞IO
    NIO:面向緩沖,非阻塞IO,有selector的支持。
    阻塞IO 讀寫的好處,每次返回都必然是讀寫完成了,適用於一個線程處理一個連接,且連接處理發送接收數據量較大的情況。
    非阻塞IO 每次讀寫返回未必是你想要的數據都讀寫完成了,即不會等待IO真正完成具體操作,因此在解析數據稍微復雜些。但是NIO由於是非阻塞IO,可以在一個線程中處理多個連接,特別適用於同時讀寫處理多個管道,且每個管道連接的收發數據量不大的情況,比如互聯網中的聊天服務器。

    • NIO中重要的是 Channel、Buffer、Selector。

    Channel就如同IO流裏面的Stream的概念。從Channel中讀入數據到Buffer,或者從Buffer寫入數據到Channel中。
    Selector支持可以同時對多個Channel進行管理,任何一個Channel發生讀寫,Selector都能夠知曉,從而做出對應處理。

    • Channel :

      • FileChannel( FileChannel無法設置為非阻塞模式,它總是運行在阻塞模式下。)、
      • DatagramChannel
      • SocketChannel
      • ServerSocketChannel
    • Buffer :緩沖區本質上是一塊可以寫入數據,然後可以從中讀取數據的內存。這塊內存被包裝成NIO Buffer對象,並提供了一組方法,用來方便的訪問該塊內存。

      Buffer有兩個模式:讀模式 和 寫模式。有三個屬性,capacity、position、limit。理解了position和limit屬性,你就能明白buffer了。

      • capacity
        是表示內存容量,與讀寫模式無關,大小固定。
      • position
        當你寫數據到Buffer中時,position表示當前的位置。初始的position值為0.當一個byte、long等數據寫到Buffer後, position 會向前移動到下一個可插入數據的Buffer單元。position最大可為capacity – 1.
        當讀取數據時,也是從某個特定位置讀。當將Buffer從寫模式切換到讀模式,position會被重置為0. 當從Buffer的position處讀取數據時,position向前移動到下一個可讀的位置。
      • limit
        在寫模式下,Buffer的limit表示你最多能往Buffer裏寫多少數據。 寫模式下,limit等於Buffer的capacity。
        當切換Buffer到讀模式時, limit表示你最多能讀到多少數據。因此,當切換Buffer到讀模式時,limit會被設置成寫模式下的position值。換句話說,你能讀到之前寫入的所有數據(limit被設置成已寫數據的數量,這個值在寫模式下就是position)。
        分配Buffer的capacity:CharBuffer buf = CharBuffer.allocate(1024);

      • 向Buffer中寫數據:
        從Channel中讀取數據寫入Buffer中: int bytesRead = inChannel.read(buf);
        從字符串寫入到Buffer中: buf.put(127);
        flip方法:flip方法將Buffer從寫模式切換到讀模式。調用flip()方法會將position設回0,並將limit設置成之前position的值。
        從buffer中讀取數據:int bytesWritten = inChannel.write(buf); byte aByte = buf.get();
        Buffer.rewind()將position設回0,所以你可以重讀Buffer中的所有數據。limit保持不變,仍然表示能從Buffer中讀取多少個元素(byte、char等)。
      • clear()
        position將被設回0,limit被設置成 capacity的值。換句話說,Buffer 被清空了。Buffer中的數據並未清除,只是這些標記告訴我們可以從哪裏開始往Buffer裏寫數據。意味著不再有任何標記會告訴你哪些數據被讀過,哪些還沒有。
        如果Buffer中仍有未讀的數據,且後續還需要這些數據,但是此時想要先先寫些數據,那麽使用compact()方法。
      • compact()
        將所有未讀的數據拷貝到Buffer起始處。然後將position設到最後一個未讀元素正後面。limit屬性依然像clear()方法一樣,設置成capacity。現在Buffer準備好寫數據了,但是不會覆蓋未讀的數據。

      • mark()與reset()方法
        mark標記position的位置,這樣即使後續在使用get之後,再使用reset方法,則position又回回到mark時候的位置。

        private static Logger logger1 = Logger.getLogger(useNIO.class);
        public static void testByteBuffer()
        {
        
              RandomAccessFile f;
              try {
                     //創建文件流
                     f = new RandomAccessFile(".\\log\\nio.txt","rw");
                     //從文件流中獲取channel
                     FileChannel channel = f.getChannel();
                     //分配1024個字節
                     ByteBuffer buf = ByteBuffer.allocate(1024);
                     int bytes = channel.read(buf);
                     while(bytes !=-1)
                     {
                           //切換buffer狀態,從寫入狀態變為讀取
                           buf.flip();
                           logger1.info("position:"+buf.position()+"  limit:"+buf.limit());
                           //標記當前position的位置,用於之後的reset
                           buf.mark();
                           while(buf.hasRemaining())
                           {
                                  logger1.info((char)buf.get());
                                  logger1.info("position:"+buf.position()+"  limit:"+buf.limit());
                           }
                           //position又回到mark時候的位置。
                           buf.reset();
                           logger1.info("after reset position:"+buf.position()+"  limit:"+buf.limit());
                           //將所有未讀的數據拷貝到Buffer起始處。然後將position設到最後一個未讀元素正後面。limit屬性依然像clear()方法一樣,設置成capacity。現在Buffer準備好寫數據了,但是不會覆蓋未讀的數據。
                           buf.compact();
                           logger1.info("after compact  position:"+buf.position()+" limit:"+buf.limit());
                           //position將被設回0,limit被設置成 capacity的值。換句話說,Buffer 被清空了。Buffer中的數據並未清除,只是這些標記告訴我們可以從哪裏開始往Buffer裏寫數據。意味著不再有任何標記會告訴你哪些數據被讀過,哪些還沒有。
                           buf.clear();
                           logger1.info("after clear position:"+buf.position()+"  limit:"+buf.limit());
                           bytes = channel.read(buf);
                     }
                     f.close();
              }
              catch (FileNotFoundException e) {
                     e.printStackTrace();
              }
              catch (Exception e)
              {
                     e.printStackTrace();
              }
        
        }
    • Selector,用於對多個Channel進行管理使用,大大提高效率的示例:

      private static ByteBuffer receiveBuffer = ByteBuffer.allocate(1024);
         public static void testSelector()
         {
                int port = 33333;
      
                Selector selector;
                try
                {
                       ServerSocketChannel server =  ServerSocketChannel.open();
                       server.configureBlocking(false);
                       server.bind(new InetSocketAddress("127.0.0.1", port));
                       selector = Selector.open();
                       server.register(selector, SelectionKey.OP_ACCEPT);
                       logger1.info("[系統消息提示]監聽端口" + port);
                       while(true)
                       {
                             try
                             {
                                    selector.select(1000); //可以設置超時時間,防止進程阻塞
                                     Set<SelectionKey> selectionKeys =  selector.selectedKeys();
                                     Iterator<SelectionKey> selectionKeyIte =  selectionKeys.iterator();
                                     while(selectionKeyIte.hasNext()){
                                             SelectionKey selectionKey =  selectionKeyIte.next();
                                             selectionKeyIte.remove();
                                             DoSomeThing(selectionKey,selector);
                                     }
                             }
                             catch(Exception e)
                             {
      
                             }
      
                       }
                }
                catch
                (Exception e)
                {
      
                }
      
         }
         public static void DoSomeThing(SelectionKey selectionKey,Selector  selector)throws IOException
         {
                 ServerSocketChannel server = null;
                 SocketChannel channel = null;
                 String receiveText;
                  int count;
                  if(selectionKey.isAcceptable()){
                         try {
                               server = (ServerSocketChannel)  selectionKey.channel();
                               channel = server.accept();
                               channel.configureBlocking(false);
                               channel.register(selector, SelectionKey.OP_READ);
                         }
                         catch (IOException e)
                         {
                               e.printStackTrace();
                         }
                  }
                  else if(selectionKey.isReadable())
                  { //獲取通道對象,方便後面將通道內的數據讀入緩沖區
                         channel = (SocketChannel) selectionKey.channel();
                         receiveBuffer.clear();
                         count = channel.read(receiveBuffer); //如果讀出來的客戶端數據不為空
                         if(count>0)
                         {
                               receiveText = new  String(receiveBuffer.array(),0,count);
                               logger1.info("[系統消息提示]服務器發現["+receiveText+"]new connect"); }
                         else
                         {
                               logger1.info("[系統消息提示]someone disconnect"); 
                         selectionKey.cancel();
                         }
                       }
         }

    java中NIO的介紹