Java NIO非堵塞應(yīng)用通常適用用在I/O讀寫(xiě)等方面,主要包括非阻塞,Buffer,內(nèi)存映射,塊讀取。系統(tǒng)運(yùn)行的性能瓶頸通常在I/O讀寫(xiě),包括對(duì)端口和文件的操作上,過(guò)去,在打開(kāi)一個(gè)I/O通道后,read()將一直等待在端口一邊讀取字節(jié)內(nèi)容,如果沒(méi)有內(nèi)容進(jìn)來(lái),read()也是傻傻的等,這會(huì)影響我們程序繼續(xù)做其他事情,那么改進(jìn)做法就是開(kāi)設(shè)線(xiàn)程,讓線(xiàn)程去等待,但是這樣做也是相當(dāng)耗費(fèi)資源的。

Java NIO非堵塞技術(shù)實(shí)際是采取Reactor模式,或者說(shuō)是Observer模式為我們監(jiān)察I/O端口,如果有內(nèi)容進(jìn)來(lái),會(huì)自動(dòng)通知我們,這樣,我們就不必開(kāi)啟多個(gè)線(xiàn)程死等,從外界看,實(shí)現(xiàn)了流暢的I/O讀寫(xiě),不堵塞了。

 

原來(lái)的 I/O 以流的方式處理數(shù)據(jù),而 NIO 以塊的方式處理數(shù)據(jù)。 面向流 的 I/O 系統(tǒng)一次一個(gè)字節(jié)地處
理數(shù)據(jù)。一個(gè)輸入流產(chǎn)生一個(gè)字節(jié)的數(shù)據(jù),一個(gè)輸出流消費(fèi)一個(gè)字節(jié)的數(shù)據(jù)。為流式數(shù)據(jù)創(chuàng)建過(guò)濾器非常容易。鏈接幾個(gè)過(guò)濾器,以便每個(gè)過(guò)濾器只負(fù)責(zé)單個(gè)復(fù)雜處理機(jī)制的一部分,這樣也是相對(duì)簡(jiǎn)單的。不利的一面是,面向流的 I
/O 通常相當(dāng)慢。 一個(gè) 面向塊 的 I/O 系統(tǒng)以塊的形式處理數(shù)據(jù)。每一個(gè)操作都在一步中產(chǎn)生或者消費(fèi)一個(gè)數(shù)據(jù)塊。按塊處理數(shù)據(jù)比按(流式的)字節(jié)處理數(shù)據(jù)要快得多。但是面向塊的 I/O 缺少一些面向流的I/O 所具有的優(yōu)雅性和簡(jiǎn)單性。 


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

NIO主要原理和適用。

NIO 有一個(gè)主要的類(lèi)Selector,這個(gè)類(lèi)似一個(gè)觀察者,只要我們把需要探知的socketchannel告訴Selector,我們接著做別的事情,當(dāng)有事件發(fā)生時(shí),他會(huì)通知我們,傳回一組SelectionKey,我們讀取這些Key,就會(huì)獲得我們剛剛注冊(cè)過(guò)的socketchannel,然后,我們從這個(gè)Channel中讀取數(shù)據(jù),放心,包準(zhǔn)能夠讀到,接著我們可以處理這些數(shù)據(jù)。

Selector內(nèi)部原理實(shí)際是在做一個(gè)對(duì)所注冊(cè)的channel的輪詢(xún)?cè)L問(wèn),不斷的輪詢(xún)(目前就這一個(gè)算法),一旦輪詢(xún)到一個(gè)channel有所注冊(cè)的事情發(fā)生,比如數(shù)據(jù)來(lái)了,他就會(huì)站起來(lái)報(bào)告,交出一把鑰匙,讓我們通過(guò)這把鑰匙來(lái)讀取這個(gè)channel的內(nèi)容。

首先簡(jiǎn)單的印象是NIO快,所以想寫(xiě)個(gè)程序驗(yàn)證一下.如下復(fù)制:

  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);
     }
 

本以為可以結(jié)束,結(jié)果測(cè)試結(jié)果出乎意料,函數(shù)一比函數(shù)二要快,就是說(shuō)Old IO快于NIO ,從此也就開(kāi)始了整個(gè)過(guò)程:


 為了了解這個(gè)問(wèn)題,仔細(xì)搜索并仔細(xì)再看IBM 的NIO教程,看到如下這段話(huà)
 ---------------------------------------------
 在 JDK 1.4 中原來(lái)的 I/O 包和 NIO 已經(jīng)很好地集成了。 java.io.* 已經(jīng)以 NIO 為基礎(chǔ)重新實(shí)現(xiàn)了,
 所以現(xiàn)在它可以利用 NIO 的一些特性。例如, java.io.* 包中的一些類(lèi)包含以塊的形式讀寫(xiě)數(shù)據(jù)的方法,
 這使得即使在更面向流的系統(tǒng)中,處理速度也會(huì)更快。 也可以用 NIO 庫(kù)實(shí)現(xiàn)標(biāo)準(zhǔn) I/O 功能。例如,
 可以容易地使用塊 I/O 一次一個(gè)字節(jié)地移動(dòng)數(shù)據(jù)。但是正如您會(huì)看到的,NIO 還提供了原 I/O 包中所沒(méi)有的許多好處。
    ---------------------------------------------

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

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);

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

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

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

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

      
//Selector傳回一組SelectionKeys
      
//我們從這些key中的channel()方法中取得我們剛剛注冊(cè)的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());

         
//一個(gè)key被處理完成后,就都被從就緒關(guān)鍵字(ready keys)列表中除去
         i.remove();
         
if(s.isAcceptable())
         
{
           
// 從channel()中取得我們剛剛注冊(cè)的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();
  }

}


}





這是一個(gè)守候在端口9000的noblock server例子,如果我們編制一個(gè)客戶(hù)端程序,就可以對(duì)它進(jìn)行互動(dòng)操作,或者使用telnet 主機(jī)名 90000 可以鏈接上。

通過(guò)仔細(xì)閱讀這個(gè)例程,相信你已經(jīng)大致了解NIO的原理和使用方法,下一篇,我們將使用多線(xiàn)程來(lái)處理這些數(shù)據(jù),再搭建一個(gè)自己的Reactor模式。