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快,所以想写个程序验证一下.如下复制:
publicstaticvoidtest2(String name1, String name2)
{
longstart=System.currentTimeMillis();
try
{
FileInputStream fis=newFileInputStream(name1);
FileOutputStream fos=newFileOutputStream(name2);
byte[] buf=newbyte[8129];
while(true)
{
intn=fis.read(buf);
if(n==-1)
{
break;
}
fos.write(buf,0,n);
}
fis.close();
fos.close();
}catch(Exception e)
{
e.printStackTrace();
}
longend=System.currentTimeMillis();
longtime=end-start;
System.out.println(time);
}
publicstaticvoidtest3(String name1, String name2)
{
longstart=System.currentTimeMillis();
try
{
FileInputStream in=newFileInputStream(name1);
FileOutputStream out=newFileOutputStream(name2);
FileChannel fc1=in.getChannel();
FileChannel fc2=out.getChannel();
ByteBuffer bb=ByteBuffer.allocate(8129);
while(true)
{
bb.clear();
intn=fc1.read(bb);
if(n==-1)
{
break;
}
bb.flip();
fc2.write(bb);
}
fc1.close();
fc2.close();
}catch(IOException e)
{
}
longend=System.currentTimeMillis();
longtime=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 包中所没有的许多好处。
---------------------------------------------
了解了这个基本原理,我们结合代码看看使用,在使用上,也在分两个方向,一个是线程处理,一个是用非线程,后者比较简单,看下面代码:
importjava.io.*;
importjava.nio.*;
importjava.nio.channels.*;
importjava.nio.channels.spi.*;
importjava.net.*;
importjava.util.*;
/** *//**
*
*@authorAdministrator
*@version
*/
publicclassNBTest
{
/** *//**Creates new NBTest*/
publicNBTest()
{
}
publicvoidstartServer()throwsException
{
intchannels=0;
intnKeys=0;
intcurrentSelector=0;
//使用Selector
Selector selector=Selector.open();
//建立Channel 并绑定到9000端口
ServerSocketChannel ssc=ServerSocketChannel.open();
InetSocketAddress address=newInetSocketAddress(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.");
}
}
}
privatestaticvoiddebug(String s)
{
System.out.println(s);
}
privatestaticvoidprintKeyInfo(SelectionKey sk)
{
String s=newString();
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);
}
/** *//**
*@paramargs the command line arguments
*/
publicstaticvoidmain (String args[])
{
NBTest nbTest=newNBTest();
try
{
nbTest.startServer();
}
catch(Exception e)
{
e.printStackTrace();
}
}
}
这是一个守候在端口9000的noblock server例子,如果我们编制一个客户端程序,就可以对它进行互动操作,或者使用telnet 主机名 90000 可以链接上。
通过仔细阅读这个例程,相信你已经大致了解NIO的原理和使用方法,下一篇,我们将使用多线程来处理这些数据,再搭建一个自己的Reactor模式。