快速弄懂Java 11 中的NIO 2.0( 二 )

编译这段代码,然后通过类似于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 memoryMappedByteBuffer out = memoryMappedFile.getChannel().map(FileChannel.MapMode.READ_WRITE, 0, mem_map_size);//Writing into Memory Mapped Filefor (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更加灵活,并且能够在更底层去实现,这样就能够获得更好的性能 。这个对于搜索那些对于编码、顺序以及其他语言特点比较敏感的非英语语言而言更加有价值 。


推荐阅读