早期的I/O通信使用Stream编程实现,流I/O可用于内外部通信但需要把对象转换成byte。NIO使用了块级I/O,其目的是为了让
java 程序可以实现高速 I/O 而无需编写自定义的本机代码。NIO 将最耗时的 I/O 操作(即填充和提取缓冲区)转移回操作系统,因而可以极大地提高速度。
1) 面向流I/O的系统,一次处理一个字节的数据。一个输入流每次会读入一个字节的数据,一个输出流同样每次次消费一个字节的数据。对于流式数据,很容易创建过滤器。可以相对简单地把几个过滤器连接在一起,每个过滤器完成自己的工作,也是按字节进行过滤,精细的处理机制。另一方面,面向流I/-O的通信往往比较缓慢。
2) 面向块I/O的系统,以块为单位处理数据。每个操作步骤会生成或消费一个块的数据。以块为单位处理数据,其处理速度远快于以字节流为单位的方式。但是,与面向流I/O的通信相比,面向块I/O的通信缺乏优雅和简洁。
NIO中的两个核心对象:通道(Channel)和缓冲区(Buffer)
通道是对原 I/O 包中的流的模拟。到任何目的地(或来自任何地方)的所有数据都必须通过一个 Channel 对象。一个 Buffer 实质上是一个容器对象。发送给一个通道的所有对象都必须首先放到缓冲区中;同样地,从通道中读取的任何数据都要读到缓冲区中。通道与流的不同之处在于通道是双向的。而流只是在一个方向上移动(一个流必须是 InputStream 或者 OutputStream 的子类), 而 通道 可以用于读、写或者同时用于读写。
缓冲区实质上是一个数组。通常它是一个字节数组,但是也可以使用其他种类的数组。但是一个缓冲区不 仅仅 是一个数组。缓冲区提供了对数据的结构化访问,而且还可以跟踪系统的读/写进程。
举个文件读写使用NIO的例子:
[
java]
FileInputStream fin = new FileInputStream( "readandshow.txt" );
FileChannel fc = fin.getChannel();
ByteBuffer buffer = ByteBuffer.allocate( 1024 );
fc.read( buffer );
FileOutputStream fout = new FileOutputStream( "writesomebytes.txt" );
FileChannel fc = fout.getChannel();
ByteBuffer buffer = ByteBuffer.allocate( 1024 );
for (int i=0; i<message.length; ++i)
buffer.put( message );
buffer.flip();
不需要告诉通道要读入或者写入多少数据。缓冲区的内部统计机制会跟踪它包含多少数据以及还有多少数据要读入或者写入。
异步I/O也是依赖于NIO
技术实现的一种网络IO解决方案,它也同样依赖于通道和缓冲区,可以无阻塞的读写数据,还可以
注册监听特定的I/O事件,比如可读数据的到达、产生的新的连接等等。异步I/O允许您同时根据大量的输入和输出执行 I/O,监听任何数量的通道上的事件,不用轮询,也不用额外的线程。而传统的同步方式常常要求助于轮询,或者创建许许多多的线程以处理大量的连接。
异步I/O中的核心对象是Selector,靠它可以注册各种感兴趣的I/O事件(观察者模式)。下面是一个很不错的例子:
[
java]
Selector selector = Selector.open();
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.configureBlocking( false );
ServerSocket ss = ssc.socket();<span style="white-space:pre"> </span>//accept a connection made to this channel's socket
InetSocketAddress address = new InetSocketAddress( ports );
ss.bind( address )
//whenever a socket set up, notify user
SelectionKey key = ssc.register( selector, SelectionKey.OP_ACCEPT );
Set selectedKeys = selector.selectedKeys();
Iterator it = selectedKeys.iterator();
while (it.hasNext()) {
SelectionKey key = (SelectionKey)it.next();
if ((key.readyOps() ; SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT) {
ServerSocketChannel ssc = (ServerSocketChannel)key.channel();
//will not be blocked
SocketChannel sc = ssc.accept();
//each socket channel must be set non-blocking
sc.configureBlocking( false );
//can also register any accepted socket channel on the same selector
SelectionKey newKey = sc.register( selector, SelectionKey.OP_READ );
//...read and write data from socket
}
}
关于何时该采用传统io,何时应该采用nio:
1) 扩展性考虑:例如在进行Socket编程通信时每一个Socket都应占据一个线程。使用NIO虽然更富有效率,但相对难以编码和扩展。(当然这一现象在不断的被新的设计和NIO库的特性所改善)
2) 性能考虑:在处理成千上万的连接时,你可能需要更好的传统IO的扩展性;但是如果连接数量较低时,你可能更注重NIO的高吞吐率。
3) 当使用SSL (Secure Sockets Layer,
安全套接字层) 工作时,选择NIO则实现难度很大
对于
java NIO包,已经有Grizzly 和Quick Server项目提供了可重用的非阻塞式的
服务器组件。