Java NIO的核心组件简介和使用

图片来自nidan-455298会员

在Java Socket通信中,Java NIO是一个很重要的底层实现类,理解其核心组件和使用方法,将会帮助了解更多在Java NIO基础之上搭建的其它Socket通信应用,例如Netty IO等。

本文将对Java NIO 核心组件做一个简单介绍,读完本文,你将可以读Java NIO有个总体上的认识,并能够通过代码样例,编程实现一个多线程异步消息通信。

1. Java NIO概览

Java NIO全称是Java New Input/Output,是很早在JDK1.4中引入的输入输出类库,之后在JDK7中提供了升级版的NIO2,提供了异步编程模型的IO操作。下文若没有说明,Java NIO是指在JDK7之后的最新NIO类库。

有一个首先需要了解的问题是,为什么会有NIO?和之前的IO类库相比,其带来了哪些优点和改进?

在JDK1.4之前,所有IO操作都是基于流(stream)进行数据读写,其读写操作方法会阻塞,这将大大影响程序的效率。Java NIO一方面解决了阻塞的问题,另一方面对数据块存储、数据处理方面进行了抽象,提供了缓存区、通信通道和选择器三个组件概念,使得程序开发者可以在更深入的层次上介入IO处理过程,对相关的通信进行多线程优化,编程可扩展性大大提高。

下表列出IO和NIO在主要特性上的区别对比,

IO NIO
操作对象 面向流 面向缓存区
操作特性 单向操作,或读,或写 支持读写双向操作
读写单位 按字节一一进行读/写 Bytes 按指定块存储大小进行读/写 Block Buffer
支持非阻塞 只支持阻塞读写 支持阻塞和非阻塞读写
通信通道 无,一个流本身就是一个通道 基于通道进行数据传输
单线程 多通道 不支持 支持,通过选择器实现一个线程处理多个通道
异步编程 不支持 支持异步编程模型(自JDK7)

可以看到Java NIO在IO操作上进行了很大幅度的改进和提升。

在Java NIO类库中,有四个核心组件接口,

  1. Channel通道
  2. Buffer缓存区
  3. Selector 选择器
  4. CompletionHandler 异步回调处理器

本文将对这四个组件逐一进行讲解,了解其作用和使用方法,并提供简单的代码使用样例。最后给出一个多线程异步通信的使用样例。

2. 通道Channel

一个通道是数据传输的连接,可以和IO设备建立连接,进行数据的获取和传送。在一定程度上,可以认为通道是流的升级版实现。

整个Java NIO中最常用的通道类有如下几个,

  • FileChannel,since 1.4
  • DatagramChannel,since 1.4
  • SocketChannel,since 1.4
  • ServerSocketChannel,since 1.4
  • AsynchronousFileChannel,since 1.7
  • AsynchronousSocketChannel,since 1.7
  • AsynchronousServerSocketChannel,since 1.7

其中Asynchronous*这几个类是在JDK7开始提供的NIO 2.0类库,主要提供了异步编程模型,详细见后面异步回调处理器的讨论。

(注:AsynchronousDatagramChannel在JDK7中并没有提供,原先是有准备发布这个类库的,但是由于某些原因,在发布前被删除。)

通道类中使用最多的四个方法是,

  • Channel.bind() 绑定连接
  • Channel.accept() 建立连接
  • Channel.read(buffer) 把数据从缓冲区读到通道
  • Channel.write(buffer) 把数据从通道写到缓冲区

一个简单的代码样例如下,

// please note: configure channel blocking state as false
ServerSocketChannel server = ServerSocketChannel.open();
server.configureBlocking(false);

System.out.println("A server is started on port 9000");
ServerSocket serverSocket = server.socket();
serverSocket.bind(new InetSocketAddress(9000));

// please note: server.accept() will not block current thread
// Accept method will return null directly if no client is connected
SocketChannel channel = null;
int count = 0;
while (channel == null) {
    System.out.println("The server is trying to connect client, count=" + count++);
    channel = server.accept();
    Thread.sleep(500);
}
//channel.read(ByteBuffer)

System.out.println("the end");
channel.close();
server.close();

3. 缓冲区Buffer

Java NIO是基于块进行IO操作,缓冲区就是对这个块的抽象定义,在这个缓冲区中可以反复进行读写,通过通道实现数据的传输。

我们先看看缓冲区是什么样子,一个缓冲区是一个有指定大小的数组,其有读模式和写模式两种状态,请注意在读写不同模式下其Position和Limit的位置指向, 上图的缓冲区中显示了如下三个属性,

  • Capacity:缓冲区容量大小
  • Position:缓冲区的读写位置,根据当前读/写模式,含义如下
    • 在读模式下,读的当前位置
    • 在写模式下,写的当前位置
  • Limit:缓冲区的读写限制位,根据当前读/写模式,含义如下
    • 在读模式下,可以读的最大位置,等于当前缓冲区内数据量,防止读溢出
    • 在写模式下,可以写的最大位置,其实就等于Capacity值,防止写溢出

Java的测试覆盖率工具

对于Java语言的测试覆盖率工具众多,有开源免费的Jacoco/PIT,也有商业的Clover,也有开发了十多年目前还在发布的的覆盖率工具Cobertura,

  1. Emma (http://eclemma.org/jacoco/)
  2. JCov (https://wiki.openjdk.java.net/display/CodeTools/jcov)
  3. Code Cover (http://codecover.org/)
  4. Cobertura (http://cobertura.github.io/cobertura)
  5. PIT (http://pitest.org/)
  6. Clover (https://www.atlassian.com/software/clover)
  7. Jacoco (http://eclemma.org/jacoco/)

下图将上述覆盖率工具的功能、授权方式、代码的注入、报告和语言支持、对各个工具的集成支持、开发活跃度一一列出,方便大家进行一些比较,(其中Emma已停止开发,其被Jacoco取代,不再列入)

java_cov_tools_comparison

表格中代码注入方式的含义如下,

— source code: 在源代码编译时刻进行注入

— bytecode offline: 对编译后的类二进制文件进行注入,在JVM载入内存前

— bytecode on the fly: 在JVM载入二进制文件时使用application classloader动态注入

比较推荐使用Jacoco,各种工具的支持比其它开源工具更好,社区也很活跃,个人也在使用其开发覆盖率平台的工具,使用下来感觉还不错。

Jacoco提供了丰富的API接口,其开发文档和相关小工具还是比较多,目前主要集成在各个开发工具和构建分析工具中,开发人员一般在IDE中使用各种Jacoco插件分析代码,测试人员基本上通过ant/maven工具生成覆盖率报告,然后将报告展现在CI/Sonar等各个工具上。但是Jacoco的功能是不仅仅于此,比较遗憾的是还没有一个通用的覆盖率收集平台工具,能够为测试活动提供一个整体解决方案,对于测试,一般希望能够按阶段按环境来收集覆盖率数据,能够实时监控不仅仅来自自动化测试的数据,还可以为手动测试提供实时监测数据和报告,能够分析增量代码的覆盖率数据。目前的覆盖率工具基本以插件的方式存在,功能和报告分散,也许在不久的将来会有个覆盖率平台能够开发出来解决这个问题,当然这个覆盖率平台也一定不仅仅支持Java语言。

 

参考资料,

— Clover官方wiki对各个覆盖率工具的比较: Link

— Wiki上对各个覆盖率工具的比较:Link

— SonarQube对覆盖率工具的介绍: Link

— IBM DeveloperWorks对Jacoco的介绍:Link