Java NIO原理和使用?原来的?I/O?以流的方式处理数据,而?NIO?以块的方式处理数据。?面向流?的?I/O?系统一次
Java NIO原理和使用
?
原来的?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模式。