NIO2.0时代1. 变更通知(因为每个事件都需要一个监听者)
对NIO和NIO.2有兴趣的开发者的共同关注点在于JAVA应用的性能 。根据我的经验,NIO.2里的文件变更通知者(file change notifier)是新输入/输出API里最让人感兴趣(被低估了)的特性 。
很多企业级应用需要在下面的情况时做一些特殊的处理:
- 当一个文件上传到一个FTP文件夹里时
- 当一个配置里的定义被修改时
- 当一个草稿文档被上传时
- 其他的文件系统事件出现时
NIO.2提供了一个更好地方式来进行变更检测 。列表1是一个简单的示例 。
列表1. NIO.2里的变更通知机制
public class Watcher {public static void main(String[] args) {Path this_dir = Paths.get(".");System.out.println("Now watching the current directory ...");try {WatchService watcher = this_dir.getFileSystem().newWatchService();this_dir.register(watcher, StandardWatchEventKinds.ENTRY_CREATE);WatchKey watckKey = watcher.take();List<WatchEvent<<64;>> events = watckKey.pollEvents();for (WatchEvent event : events) {System.out.println("Someone just created the file '" + event.context().toString() + "'.");}} catch (Exception e) {System.out.println("Error: " + e.toString());}}}编译这段代码,然后在命令行里执行 。在相同的目录下,创建一个新的文件,例如运行touch example或者copy Watcher.class example命令 。你会看到下面的变更通知消息:
Someone just create the fiel ‘example1′.这个简单的示例展示了怎么开始使用Java NIO的功能 。同时,它也介绍了NIO.2的Watcher类,它相比较原始的I/O中的轮询方案而言,显得更加直接和易用 。
注意拼写错误2. 选择器和异步IO:通过选择器来提高多路复用
当你从这篇文章里拷贝代码时,注意拼写错误 。例如,列表1种的StandardWatchEventKinds 对象是复数的形式 。即使在Java.net的文档里都把它给拼写错了 。
小技巧
NIO里的通知机制比老的轮询方式使用起来更加简单,这样会诱导你忽略对具体需求的详细分析 。当你在你第一次使用一个监听器的时候,你需要仔细考虑你所使用的这些概念的语义 。例如,知道一个变更什么时候会结束比知道它什么时候开始更加重要 。这种分析需要非常仔细,尤其是像移动FTP文件夹这种常见的场景 。NIO是一个功能非常强大的包,但同时它还会有一些微妙的“陷阱”,这会给那些不熟悉它的人带来困扰 。
NIO新手一般都把它和“非阻塞输入/输出”联系在一起 。NIO不仅仅只是非阻塞I/O,不过这种认知也不完全是错的:Java的基本I/O是阻塞式I/O——意味着它会一直等待到操作完成——然而,非阻塞或者异步I/O是NIO里最常用的一个特点,而非NIO的全部 。
NIO的非阻塞I/O是事件驱动的,并且在列表1里文件系统监听示例里进行了展示 。这就意味着给一个I/O通道定义一个选择器(回调或者监听器),然后程序可以继续运行 。当一个事件发生在这个选择器上时——例如接收到一行输入——选择器会“醒来”并且执行 。所有的这些都是通过一个单线程来实现的,这和Java的标准I/O有着显著的差别的 。
列表2里展示了使用NIO的选择器实现的一个多端口的网络程序echo-er,这里是修改了Greg Travis在2003年创建的一个小程序(参考资源列表) 。Unix和类Unix系统很早就已经实现高效的选择器,它是Java网络高性能编程模型的一个很好的参考模型 。
列表2. NIO选择器
public class MultiPortEcho{private int ports[];private ByteBuffer echoBuffer = ByteBuffer.allocate( 1024 );public MultiPortEcho( int ports[] ) throws IOException {this.ports = ports;configure_selector();}private void configure_selector() throws IOException {// Create a new selectorSelector selector = Selector.open();// Open a listener on each port, and register each one// with the selectorfor (int i=0; i<ports.length; ++i) {ServerSocketChannel ssc = ServerSocketChannel.open();ssc.configureBlocking(false);ServerSocket ss = ssc.socket();InetSocketAddress address = new InetSocketAddress(ports[i]);ss.bind(address);SelectionKey key = ssc.register(selector, SelectionKey.OP_ACCEPT);System.out.println("Going to listen on " + ports[i]);}while (true) {int num = selector.select();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) {// Accept the new connectionServerSocketChannel ssc = (ServerSocketChannel)key.channel();SocketChannel sc = ssc.accept();sc.configureBlocking(false);// Add the new connection to the selectorSelectionKey newKey = sc.register(selector, SelectionKey.OP_READ);it.remove();System.out.println( "Got connection from "+sc );} else if ((key.readyOps() & SelectionKey.OP_READ)== SelectionKey.OP_READ) {// Read the dataSocketChannel sc = (SocketChannel)key.channel();// Echo dataint bytesEchoed = 0;while (true) {echoBuffer.clear();int number_of_bytes = sc.read(echoBuffer);if (number_of_bytes <= 0) {break;}echoBuffer.flip();sc.write(echoBuffer);bytesEchoed += number_of_bytes;}System.out.println("Echoed " + bytesEchoed + " from " + sc);it.remove();}}}}}
推荐阅读
- java NIO 的最佳实践
- Java程序员须知的七个日志管理工具
- JavaScript生成一个不重复的ID
- 快速进行数据库管理的Mac软件
- Android NDK-深入理解JNI
- Java 13 明天发布,最新最全新特性解读
- 单手轻弹iPhone最快速打开健康码的方式
- 快速吃瓜,什么是微信键盘
- 梦见植物长势很好 梦见植物快速生长
- 不用新手机号就能快速创建微信小号