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模式。