NIO的基本概念及简单示例
在JDK 1.4的新特性中,NIO无疑是最显著和鼓舞人心的。NIO的出现事实上意味着Java虚拟机的性能比以前的版本有了较大的飞跃。在以前的JVM的版本中,代码的执行效率不高(在最原始的版本中Java是解释执行的语言),用Java编写的应用程序通常所消耗的主要资源就是CPU,也就是说应用系统的瓶颈是CPU的计算和运行能力。在不断更新的Java虚拟机版本中,通过动态编译技术使得Java代码执行的效率得到大幅度提高,几乎和操作系统的本地语言(例如C/C++)的程序不相上下。在这种情况下,应用系统的性能瓶颈就从CPU转移到IO操作了。尤其是服务器端的应用,大量的网络IO和磁盘IO的操作,使得IO数据等待的延迟成为影响性能的主要因素。NIO的出现使得Java应用程序能够更加紧密地结合操作系统,更加充分地利用操作系统的高级特性,获得高性能的IO操作。
一、 NIO基本概念
NIO在磁盘IO处理和文件处理上有很多新的特性来提高性能,本文不作详细的解释,而仅仅介绍NIO在处理网络IO方面的新特点,这些特点是理解Grizzly的最基本的概念。
1. 数据缓冲(Buffer)处理
数据缓冲(Buffer)是IO操作的基本元素。其实从本质上来说,无论是磁盘IO还是网络IO,应用程序所作的所有事情就是把数据放到相应的数据缓冲当中去(写操作),或者从相应的数据缓冲中提取数据(读操作)。至于数据缓冲中的数据和IO设备之间的交互,则是操作系统和硬件驱动程序所关心的事情了。因此,数据缓冲在IO操作中具有重要的作用,是操作系统与应用之间的IO桥梁。在NIO的包中,Buffer类是所有类的基础。 Buffer类当中定义数据缓冲的基本操作,包括put、get、reset、clear、flip、rewind等,这些基本操作是进行数据输入输出的手段。每一个基本的Java类型(boolean除外)都有相应的Buffer类,例如CharBuffer、IntBuffer、 DoubleBuffer、ShortBuffer、LongBuffer、FloatBuffer和ByteBuffer。我们所关心的是 ByteBuffer,因为操作系统与应用程序之间的数据通信最原始的类型就是Byte
数据缓冲的另外一个重要的特点是可以在一个数据缓冲上再建立一个或多个视图(View)缓冲。这个概念有些类似于数据库视图的概念:在数据库的物理表 (Table)结构之上可以建立多个视图。同样,在一个数据缓冲之上也可以建立多个逻辑的视图缓冲。视图缓冲的用处很多,例如可以将Byte类型的缓冲当作Int类型的视图,来进行类型转换。视图缓冲也可以将一个大的缓冲看成是很多小的缓冲视图。这对提高性能很有帮助,因为创建物理的数据缓冲(特别是直接的数据缓冲)是非常耗时的操作,而创建视图却非常快。在Grizzly中就有这方面的考虑。
2. 异步通道(Channel)
Channel(后文又称频道,译法仅暗示存在多通道可选)是NIO的另外一个比较重要的新特点。Channel并不是对原有Java类的扩充和完善,而是完全崭新的实现。通过Channel,Java应用程序能够更好地与操作系统的IO服务结合起来,充分地利用上文提到的ByteBuffer,完成高性能的IO操作。Channel的实现也不是纯Java的,而是和操作系统结合紧密的本地代码。
Channel的一个重要的特点是在网络套接字频道(SocketChannel)中,可以将其设置为异步非阻塞的方式。
非阻塞方式的频道使用:
public class Server { public static void main(String[] argv) throws Exception { ServerSocketChannel serverCh = ServerSocketChannel.open(); Selector selector = Selector.open(); ServerSocket serverSocket = serverCh.socket(); serverSocket.bind(new InetSocketAddress(80)); serverCh.configureBlocking(false); serverCh.register(selector,SelectionKey.OP_ACCEPT); while(true){ selector.select(); Iterator it = selector.selectedKeys().iterator(); while (it.hasNext()) { SelectionKey key = (SelectionKey)it.next(); if (key.isAcceptable()) {ServerSocketChannel server =(ServerSocketChannel)key.channel(); SocketChannel channel = server.accept(); channel.configureBlocking(false); channel.register(selector,SelectionKey.OP_READ); } if (key.isReadable()) { readDataFromSocket(key); } it.remove(); } }}}