您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > JAVA

快速弄懂Java 11 中的NIO 2.0

时间:2019-09-17 09:12:11  来源:  作者:

NIO2.0时代

1. 变更通知(因为每个事件都需要一个监听者)

对NIO和NIO.2有兴趣的开发者的共同关注点在于JAVA应用的性能。根据我的经验,NIO.2里的文件变更通知者(file change notifier)是新输入/输出API里最让人感兴趣(被低估了)的特性。

很多企业级应用需要在下面的情况时做一些特殊的处理:

  • 当一个文件上传到一个FTP文件夹里时
  • 当一个配置里的定义被修改时
  • 当一个草稿文档被上传时
  • 其他的文件系统事件出现时

这些都是变更通知或者变更响应的例子。在Java(以及其他语言)的早期版本里,轮询(polling)是检测这些变更事件的最好方式。轮询是一种特殊的无限循环:检查文件系统或者其他对象,并且和之前的状态对比,如果没有变化,在大概几百个毫秒或者10秒的间隔后,继续检查。就这一直无限循环下去。

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中的轮询方案而言,显得更加直接和易用。

注意拼写错误

当你从这篇文章里拷贝代码时,注意拼写错误。例如,列表1种的StandardWatchEventKinds 对象是复数的形式。即使在Java.net的文档里都把它给拼写错了。

小技巧

NIO里的通知机制比老的轮询方式使用起来更加简单,这样会诱导你忽略对具体需求的详细分析。当你在你第一次使用一个监听器的时候,你需要仔细考虑你所使用的这些概念的语义。例如,知道一个变更什么时候会结束比知道它什么时候开始更加重要。这种分析需要非常仔细,尤其是像移动FTP文件夹这种常见的场景。NIO是一个功能非常强大的包,但同时它还会有一些微妙的“陷阱”,这会给那些不熟悉它的人带来困扰。

2. 选择器和异步IO:通过选择器来提高多路复用

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 selector 
 Selector selector = Selector.open(); 
 
 // Open a listener on each port, and register each one 
 // with the selector 
 for (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 connection 
 ServerSocketChannel ssc = (ServerSocketChannel)key.channel(); 
 SocketChannel sc = ssc.accept(); 
 sc.configureBlocking(false); 
 
 // Add the new connection to the selector 
 SelectionKey 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 data 
 SocketChannel sc = (SocketChannel)key.channel(); 
 
 // Echo data 
 int 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 MultiPortEcho 8005 8006这样的命令来启动它。一旦这个程序运行成功,启动一个简单的telnet或者其他的终端模拟器来连接8005和8006接口。你会看到这个程序会回显它接收到的所有字符——并且它是通过一个Java线程来实现的。

java 11的新 Selector 回调方式

public int select(Consumer<SelectionKey> action, long timeout)
 throws IOException
{
 if (timeout < 0)
 throw new IllegalArgumentException("Negative timeout");
 return doSelect(Objects.requireNonNull(action), timeout);
}
public int select(Consumer<SelectionKey> action) throws IOException {
 return select(action, 0);
}
public int selectNow(Consumer<SelectionKey> action) throws IOException {
 return doSelect(Objects.requireNonNull(action), -1);
}

java 11新的 SelectionKey

ops为感兴趣的事件

public int interestOpsOr(int ops) {
 synchronized (this) {
 int oldVal = interestOps();
 interestOps(oldVal | ops);
 return oldVal;
 }
}

ops为感兴趣的事件

public int interestOpsAnd(int ops) {
 synchronized (this) {
 int oldVal = interestOps();
 interestOps(oldVal & ops);
 return oldVal;
 }
}

3. 通道:承诺与现实

在NIO里,一个通道(channel)可以表示任何可以读写的对象。它的作用是为文件和套接口提供抽象。NIO通道支持一系列一致的方法,这样就使得编码的时候不需要去特别关心不同的对象,无论它是标准输出,网络连接还是正在使用的通道。通道的这个特性是继承自Java基本I/O中的流(stream)。流(stream)提供了阻塞式的IO;通道支持异步I/O。

NIO经常会因为它的性能高而被推荐,不过更准确地是因为它的响应快速。在有些场景下NIO会比基本的Java I/O的性能要差。例如,对于一个小文件的简单的顺序读写,简单通过流来实现的性能可能比对应的面向事件的基于通道的编码实现的快两到三倍。同时,非多路复用(non-multiplex)的通道——也就是每个线程一个单独的通道——要比多个通道把各自的选择器注册在同一个线程里要慢多了。

下面你在考虑是使用流还是通道的时候,试着问自己下面几个问题:

  • 你需要读写多少个I/O对象?
  • 不同的I/O对象直接是否有有顺序,还是他们都需要同时发生的?
  • 你的I/O对象是需要持续一小段时间还是在你的进程的整个声明周期都存在?
  • 你的I/O是适合在单个线程里处理还是在几个不同的线程里?
  • 网络通信和本地I/O是看起来一样,还是各自有着不同的模式?

这样的分析是决定使用流还是通道的一个最佳实践。记住:NIO和NIO.2不是基本I/O的替代,而它的一个补充。

4. 内存映射——好钢用在刀刃上

NIO里对性能提升最显著的是内存映射(memory mApping)。内存映射是一个系统层面的服务,它把程序里用到的文件的一段当作内存来处理。

内存映射存在很多潜在的影响,比我这里提供的要多。在一个更高的层次上,它能够使得文件访问的I/O的性能达到内存访问的速度。内存访问的速度往往比文件访问的速度快几个数量级。列表3是一个NIO内存映射的一个简单示例。

列表3. NIO里的内存映射

 public class mem_map_example { 
 private static int mem_map_size = 20 * 1024 * 1024; 
 private static String fn = "example_memory_mapped_file.txt"; 
 
 public static void main(String[] args) throws Exception { 
 RandomaccessFile memoryMappedFile = new RandomAccessFile(fn, "rw"); 
 
 //Mapping a file into memory 
 MappedByteBuffer out = memoryMappedFile.getChannel().map(FileChannel.MapMode.READ_WRITE, 0, mem_map_size); 
 
 //Writing into Memory Mapped File 
 for (int i = 0; i < mem_map_size; i++) { 
 out.put((byte) 'A'); 
 } 
 System.out.println("File '" + fn + "' is now " + Integer.toString(mem_map_size) + " bytes full."); 
 
 // Read from memory-mapped file. 
 for (int i = 0; i < 30 ; i++) { 
 System.out.print((char) out.get(i)); 
 } 
 System.out.println("nReading from memory-mapped file '" + fn + "' is complete."); 
 } 
 }

在列表3中,这个简单的示例创建了一个20M的文件example_memory_mapped_file.txt,并且用字符A对它进行填充,然后读取前30个字节。在实际的应用中,内存映射不仅仅擅长提高I/O的原始速度,同时它也允许多个不同的reader和writer同时处理同一个文件镜像。这个技术功能强大但是也很危险,不过如果正确使用的话,它会使得你的IO速度提高数倍。众所周知,华尔街的交易操作为了能够赢得秒级甚至是毫秒级的优势,都使用了内存映射技术。

5. 字符编码和搜索

我在这篇文章里要讲解的NIO的最后一个特性是charset,一个用来转换不同字符编码的包。在NIO之前,Java通过getByte方法内置实现了大部分相同的功能。charset很受欢迎,因为它比getBytes更加灵活,并且能够在更底层去实现,这样就能够获得更好的性能。这个对于搜索那些对于编码、顺序以及其他语言特点比较敏感的非英语语言而言更加有价值。

列表4展示了一个把Java里的Unicode字符转换成Latin-1的示例

列表4. NIO里的字符

String some_string = "This is a string that Java natively stores as Unicode."; 
 Charset latin1_charset = Charset.forName("ISO-8859-1"); 
 CharsetEncode latin1_encoder = charset.newEncoder(); 
 ByteBuffer latin1_bbuf = latin1_encoder.encode(CharBuffer.wrap(some_string));

注意Charset和通道被设计成能够放在一起进行使用,这样就能够使得程序在内存映射、异步I/O以及编码转换进行协作的时候,能够正常运行。

总结:当然还有更多需要去了解

这篇文章的目的是为了让Java开发者能够熟悉NIO和NIO.2里的一些最主要(也是最有用)的功能。你可以通过这些示例建立起来的一些基础来理解NIO的一些其他方法;例如,你所学习的关于通道的知识能够帮助你去理解NIO的Path里对于文件系统里的符号链接的处理。你也可以参考一下我后面给出的资源列表,里面给出了一些深入学习Java新I/O API的文档。



Tags:Java NIO   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
本系列为 Netty 学习笔记,本篇介绍总结Java NIO 网络编程。Netty 作为一个异步的、事件驱动的网络应用程序框架,也是基于NIO的客户、服务器端的编程框架。其对 Java NIO 底层...【详细内容】
2021-12-07  Tags: Java NIO  点击:(17)  评论:(0)  加入收藏
NIO的三大核心配件 channel(通道)Buffer(缓冲区)selector(选择器) 案例介绍读写切换 public static void main(String[] args) { //创建一个Buffer,大小为5,可就是可以存放...【详细内容】
2021-04-16  Tags: Java NIO  点击:(304)  评论:(0)  加入收藏
在Java 7,AsynchronousFileChannel 被添加到了Java NIO中。使用AsynchronousFileChannel可以实现异步地读取和写入文件数据。创建一个AsynchronousFileChannel我们可以使用As...【详细内容】
2020-08-06  Tags: Java NIO  点击:(49)  评论:(0)  加入收藏
NIO(1)基本介绍1)Java NIO全程 java non-blocking IO,是指JDK提供的新API。从JDK1.4开始,Java提供了一系列改进的输入/输出的新特性,被统称为NIO,是同步非阻塞的2)NIO相关类都被放在...【详细内容】
2019-12-11  Tags: Java NIO  点击:(54)  评论:(0)  加入收藏
NIO2.0时代1. 变更通知(因为每个事件都需要一个监听者)对NIO和NIO.2有兴趣的开发者的共同关注点在于Java应用的性能。根据我的经验,NIO.2里的文件变更通知者(file change notifi...【详细内容】
2019-09-17  Tags: Java NIO  点击:(137)  评论:(0)  加入收藏
NIO的背景为什么一个已经存在10年的增强包还是Java的新I/O包呢?原因是对于大多数的Java程序员而言,基本的I/O操作都能够胜任。在日常工作中,大部分的Java开发者没有必要去学习N...【详细内容】
2019-09-17  Tags: Java NIO  点击:(147)  评论:(0)  加入收藏
概述 在使用Java NIO和多线程来进行高并发Java服务端应用程序设计时,通常是基于Reactor线程模型来设计的。Reactor,即包含一个Java NIO的多路复用选择器Selector的反应堆,当有...【详细内容】
2019-08-28  Tags: Java NIO  点击:(267)  评论:(0)  加入收藏
Java 之所以能够霸占编程语言的榜首,其强大、丰富的类库功不可没,几乎所有的编程问题都能在其中找到解决方案。但在早期的版本当中,输入输出(I/O)流并不那么令开发者感到愉快:1)J...【详细内容】
2019-07-24  Tags: Java NIO  点击:(273)  评论:(0)  加入收藏
Java NIO全称Java non-blocking IO,是指jdk1.4 及以上版本里提供的新api(New IO) ,为所有的原始类型(boolean类型除外)提供缓存支持的数据容器,使用它可以提供非阻塞式的高伸缩性网...【详细内容】
2019-07-22  Tags: Java NIO  点击:(424)  评论:(0)  加入收藏
▌简易百科推荐
面向对象的特征之一封装 面向对象的特征之二继承 方法重写(override/overWrite) 方法的重载(overload)和重写(override)的区别: 面向对象特征之三:多态 Instanceof关键字...【详细内容】
2021-12-28  顶顶架构师    Tags:面向对象   点击:(2)  评论:(0)  加入收藏
一、Redis使用过程中一些小的注意点1、不要把Redis当成数据库来使用二、Arrays.asList常见失误需求:把数组转成list集合去处理。方法:Arrays.asList 或者 Java8的stream流式处...【详细内容】
2021-12-27  CF07    Tags:Java   点击:(3)  评论:(0)  加入收藏
文章目录 如何理解面向对象编程? JDK 和 JRE 有什么区别? 如何理解Java中封装,继承、多态特性? 如何理解Java中的字节码对象? 你是如何理解Java中的泛型的? 说说泛型应用...【详细内容】
2021-12-24  Java架构师之路    Tags:JAVA   点击:(5)  评论:(0)  加入收藏
大家好!我是老码农,一个喜欢技术、爱分享的同学,从今天开始和大家持续分享JVM调优方面的经验。JVM调优是个大话题,涉及的知识点很庞大 Java内存模型 垃圾回收机制 各种工具使用 ...【详细内容】
2021-12-23  小码匠和老码农    Tags:JVM调优   点击:(12)  评论:(0)  加入收藏
前言JDBC访问Postgresql的jsonb类型字段当然可以使用Postgresql jdbc驱动中提供的PGobject,但是这样在需要兼容多种数据库的系统开发中显得不那么通用,需要特殊处理。本文介绍...【详细内容】
2021-12-23  dingle    Tags:JDBC   点击:(13)  评论:(0)  加入收藏
Java与Lua相互调用案例比较少,因此项目使用需要做详细的性能测试,本内容只做粗略测试。目前已完成初版Lua-Java调用框架开发,后期有时间准备把框架进行抽象,并开源出来,感兴趣的...【详细内容】
2021-12-23  JAVA小白    Tags:Java   点击:(11)  评论:(0)  加入收藏
Java从版本5开始,在 java.util.concurrent.locks包内给我们提供了除了synchronized关键字以外的几个新的锁功能的实现,ReentrantLock就是其中的一个。但是这并不意味着我们可...【详细内容】
2021-12-17  小西学JAVA    Tags:JAVA并发   点击:(11)  评论:(0)  加入收藏
一、概述final是Java关键字中最常见之一,表示“最终的,不可更改”之意,在Java中也正是这个意思。有final修饰的内容,就会变得与众不同,它们会变成终极存在,其内容成为固定的存在。...【详细内容】
2021-12-15  唯一浩哥    Tags:Java基础   点击:(17)  评论:(0)  加入收藏
1、问题描述关于java中的日志管理logback,去年写过关于logback介绍的文章,这次项目中又优化了下,记录下,希望能帮到需要的朋友。2、解决方案这次其实是碰到了一个问题,一般的情况...【详细内容】
2021-12-15  软件老王    Tags:logback   点击:(19)  评论:(0)  加入收藏
本篇文章我们以AtomicInteger为例子,主要讲解下CAS(Compare And Swap)功能是如何在AtomicInteger中使用的,以及提供CAS功能的Unsafe对象。我们先从一个例子开始吧。假设现在我们...【详细内容】
2021-12-14  小西学JAVA    Tags:JAVA   点击:(22)  评论:(0)  加入收藏
相关文章
    无相关信息
最新更新
栏目热门
栏目头条