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模式。
沒(méi)有說(shuō)清楚如果多個(gè)數(shù)據(jù)同時(shí)來(lái)臨該怎么辦,是排隊(duì),還是怎么處理