Java NIO非堵塞應用通常適用用在I/O讀寫等方面,主要包括非阻塞,Buffer,內存映射,塊讀取。系統運行的性能瓶頸通常在I/O讀寫,包括對端口和文件的操作上,過去,在打開一個I/O通道后,read()將一直等待在端口一邊讀取字節內容,如果沒有內容進來,read()也是傻傻的等,這會影響我們程序繼續做其他事情,那么改進做法就是開設線程,讓線程去等待,但是這樣做也是相當耗費資源的。

Java NIO非堵塞技術實際是采取Reactor模式,或者說是Observer模式為我們監察I/O端口,如果有內容進來,會自動通知我們,這樣,我們就不必開啟多個線程死等,從外界看,實現了流暢的I/O讀寫,不堵塞了。

 

原來的 I/O 以流的方式處理數據,而 NIO 以塊的方式處理數據。 面向流 的 I/O 系統一次一個字節地處
理數據。一個輸入流產生一個字節的數據,一個輸出流消費一個字節的數據。為流式數據創建過濾器非常容易。鏈接幾個過濾器,以便每個過濾器只負責單個復雜處理機制的一部分,這樣也是相對簡單的。不利的一面是,面向流的 I
/O 通常相當慢。 一個 面向塊 的 I/O 系統以塊的形式處理數據。每一個操作都在一步中產生或者消費一個數據塊。按塊處理數據比按(流式的)字節處理數據要快得多。但是面向塊的 I/O 缺少一些面向流的I/O 所具有的優雅性和簡單性。 


 本文主要簡單介紹NIO的基本原理,在下一篇文章中,將結合Reactor模式和著名線程大師Doug Lea的一篇文章深入討論。

NIO主要原理和適用。

NIO 有一個主要的類Selector,這個類似一個觀察者,只要我們把需要探知的socketchannel告訴Selector,我們接著做別的事情,當有事件發生時,他會通知我們,傳回一組SelectionKey,我們讀取這些Key,就會獲得我們剛剛注冊過的socketchannel,然后,我們從這個Channel中讀取數據,放心,包準能夠讀到,接著我們可以處理這些數據。

Selector內部原理實際是在做一個對所注冊的channel的輪詢訪問,不斷的輪詢(目前就這一個算法),一旦輪詢到一個channel有所注冊的事情發生,比如數據來了,他就會站起來報告,交出一把鑰匙,讓我們通過這把鑰匙來讀取這個channel的內容。

首先簡單的印象是NIO快,所以想寫個程序驗證一下.如下復制:

  public   static   void  test2(String name1, String name2)   {
         
long  start  =  System.currentTimeMillis();
          
try    {
            FileInputStream fis  
=   new  FileInputStream(name1);
            FileOutputStream fos  
=   new  FileOutputStream(name2);
             
byte [] buf  =   new   byte [ 8129 ];
              
while  ( true )   {                
                 
int  n  =  fis.read(buf);
                  
if  (n  ==   - 1 )   {
                      
break ;
                 }
 
                 fos.write(buf, 
0 ,n);
             }
 
             fis.close();
            fos.close();
          }
   catch  (Exception e)   {
            e.printStackTrace();
        }
 
         
long  end  =  System.currentTimeMillis();
          
long  time  =  end  -  start;
        System.out.println(time);
     }
 
    
       
public   static   void  test3(String name1, String name2)   {
         
long  start  =  System.currentTimeMillis();
           
try    {
             FileInputStream in  
=   new  FileInputStream(name1);
             FileOutputStream out  
=   new  FileOutputStream(name2);
            FileChannel fc1  
=  in.getChannel();
            FileChannel fc2  
=  out.getChannel();
            ByteBuffer bb  
=  ByteBuffer.allocate( 8129 );
               
while  ( true )   {
                 bb.clear();
                 
int  n  =  fc1.read(bb);
                   
if  (n  ==   - 1 )   {
                      
break ;
                }
 
                 bb.flip();
                 fc2.write(bb);
             }
 
             fc1.close();
            fc2.close();
          }
   catch  (IOException e)   {
  
        }
 
          
long  end  =  System.currentTimeMillis();
          
long  time  =  end  -  start;
         System.out.println(time);
     }
 

本以為可以結束,結果測試結果出乎意料,函數一比函數二要快,就是說Old IO快于NIO ,從此也就開始了整個過程:


 為了了解這個問題,仔細搜索并仔細再看IBM 的NIO教程,看到如下這段話
 ---------------------------------------------
 在 JDK 1.4 中原來的 I/O 包和 NIO 已經很好地集成了。 java.io.* 已經以 NIO 為基礎重新實現了,
 所以現在它可以利用 NIO 的一些特性。例如, java.io.* 包中的一些類包含以塊的形式讀寫數據的方法,
 這使得即使在更面向流的系統中,處理速度也會更快。 也可以用 NIO 庫實現標準 I/O 功能。例如,
 可以容易地使用塊 I/O 一次一個字節地移動數據。但是正如您會看到的,NIO 還提供了原 I/O 包中所沒有的許多好處。
    ---------------------------------------------

了解了這個基本原理,我們結合代碼看看使用,在使用上,也在分兩個方向,一個是線程處理,一個是用非線程,后者比較簡單,看下面代碼:

import java.io.*;
import java.nio.*;
import java.nio.channels.*;
import java.nio.channels.spi.*;
import java.net.*;
import java.util.*
/**
*
@author Administrator
@version
*/

public class NBTest {


  
/** Creates new NBTest */
  
public NBTest()
  
{
  }


  
public void startServer() throws Exception
  
{
  
int channels = 0;
  
int nKeys = 0;
  
int currentSelector = 0;

  
//使用Selector
  Selector selector = Selector.open();

  
//建立Channel 并綁定到9000端口
  ServerSocketChannel ssc = ServerSocketChannel.open();
  InetSocketAddress address 
= new InetSocketAddress(InetAddress.getLocalHost(),9000); 
  ssc.socket().bind(address);

  
//使設定non-blocking的方式。
  ssc.configureBlocking(false);

  
//向Selector注冊Channel及我們有興趣的事件
  SelectionKey s = ssc.register(selector, SelectionKey.OP_ACCEPT);
  printKeyInfo(s);

  
while(true//不斷的輪詢
  {
    debug(
"NBTest: Starting select");

    
//Selector通過select方法通知我們我們感興趣的事件發生了。
    nKeys = selector.select();
    
//如果有我們注冊的事情發生了,它的傳回值就會大于0
    if(nKeys > 0)
    
{
      debug(
"NBTest: Number of keys after select operation: " +nKeys);

      
//Selector傳回一組SelectionKeys
      
//我們從這些key中的channel()方法中取得我們剛剛注冊的channel。
      Set selectedKeys = selector.selectedKeys();
      Iterator i 
= selectedKeys.iterator();
      
while(i.hasNext())
      
{
         s 
= (SelectionKey) i.next();
         printKeyInfo(s);
         debug(
"NBTest: Nr Keys in selector: " +selector.keys().size());

         
//一個key被處理完成后,就都被從就緒關鍵字(ready keys)列表中除去
         i.remove();
         
if(s.isAcceptable())
         
{
           
// 從channel()中取得我們剛剛注冊的channel。
           Socket socket = ((ServerSocketChannel)s.channel()).accept().socket();
           SocketChannel sc 
= socket.getChannel();

           sc.configureBlocking(
false);
           sc.register(selector, SelectionKey.OP_READ 
|SelectionKey.OP_WRITE);
                      System.out.println(
++channels);
         }

         
else
         
{
           debug(
"NBTest: Channel not acceptable");
         }

      }

   }

   
else
   
{
      debug(
"NBTest: Select finished without any keys.");
   }


  }


}



private static void debug(String s)
{
  System.out.println(s);
}



private static void printKeyInfo(SelectionKey sk)
{
  String s 
= new String();

  s 
= "Att: " + (sk.attachment() == null ? "no" : "yes");
  s 
+= ", Read: " + sk.isReadable();
  s 
+= ", Acpt: " + sk.isAcceptable();
  s 
+= ", Cnct: " + sk.isConnectable();
  s 
+= ", Wrt: " + sk.isWritable();
  s 
+= ", Valid: " + sk.isValid();
  s 
+= ", Ops: " + sk.interestOps();
  debug(s);
}



/**
@param args the command line arguments
*/

public static void main (String args[])
{
  NBTest nbTest 
= new NBTest();
  
try
  
{
    nbTest.startServer();
  }

    
catch(Exception e)
  
{
    e.printStackTrace();
  }

}


}





這是一個守候在端口9000的noblock server例子,如果我們編制一個客戶端程序,就可以對它進行互動操作,或者使用telnet 主機名 90000 可以鏈接上。

通過仔細閱讀這個例程,相信你已經大致了解NIO的原理和使用方法,下一篇,我們將使用多線程來處理這些數據,再搭建一個自己的Reactor模式。