一 通道
三章节的缓冲区介绍完毕后就开始进入下一个主题——通道。如果说缓冲区仅仅是客户端操作缓冲数据的港口,那么船舶和航线就是通道,通道负责将货物(缓冲数据)从两个港口间传递,已达港口的货物或许会被消费到各个商店,或许会被押到船舶上送往下一个目的地。
单单讲缓冲区是没有意义的(哼,我不信,包装数组就当是扩充API使用不行么……),NIO需要解决的是数据如何在不同客户端间流动,这是一个双向问题,因此NIO在设计通道时,复杂度远高于缓冲区设计。
但实际上真正应用到项目中的通道又十分有限,所以通道部分我会拆成两个部分,一个是通道的接口设计,也就是本章要介绍的内容;另一个是NIO已实现的通道类型,并且做示意性的使用说明介绍。
二 通道接口
之所以先介绍接口,是因为NIO对通道的设计进行了非常多的功能抽象,具体可用的通道类具备哪些特性都是由其实现的接口决定的,所以要想比较透彻的了解通道设计,必须先弄清楚整个NIO通道体系的接口设计。需要注意的是,虽然通道接口定义了实现类的行为,但是其行为的特性如何,还是要依具体实现决定,这部分内容在后续介绍可用通道类的时候再结合源码设计来说。
整个NIO通道接口派生关系如下:
三 接口说明
这部分不会把接口的设计说明的太过详细,实际上从接口的定义上并不能很明显的看出接口方法的具体行为,JDK也仅仅是在接口方法的注释上给了一些实现类需要注意的点,我查了一些资料,绝大部分的书和网文也都是对这些接口方法的注释翻译了一下……
3.1 AutoCloseable
这个接口是JKD1.7引入的,用来支持try-catch-resources语法,解决finally块中关闭异常时的异常覆盖问题。这意味这所有Channel类均具备自动关闭的特性。
注意咯,我特意提到了一句话:finally块中关闭异常时的异常覆盖!这里很多刚入门的朋友可能比较难理解,我具一个例子:
public class ExceptionTest implements AutoCloseable {
public static void main(String[] args) throws Exception {
ExceptionTest exceptionTest = null;
try {
exceptionTest = new ExceptionTest();
// 注意open会抛出异常
exceptionTest.open();
} finally {
try {
if (exceptionTest != null) {
// open异常后执行close,close继续抛出异常
exceptionTest.close();
}
} catch (Exception e) {
// open的异常就丢失了
e.printStackTrace();
}
}
}
public void open() throws IOException {
System.out.println("invoke open method");
throw new IOException("open method exception");
}
public void close() throws Exception {
System.out.println("invoke close method");
throw new IOException("close method exception");
}
}
输出结果如下:
invoke open method
invoke close method
java.io.IOException: close method exception
at com.demo.nio.ExceptionTest.close(ExceptionTest.java:36)
at com.demo.nio.ExceptionTest.main(ExceptionTest.java:19)
Exception in thread "main" java.io.IOException: open method exception
at com.demo.nio.ExceptionTest.open(ExceptionTest.java:31)
at com.demo.nio.ExceptionTest.main(ExceptionTest.java:14)
上面的示例程序你一定不陌生,JDBC处理连接关闭的时候常常会用到上面的写法,实际上当关闭SQL连接时未必不会出现其他异常,那么在finally中关闭资源时经常会丢失最初的异常信息,而try-catch-resource语法可以规避这个问题:
public class ExceptionTest implements AutoCloseable {
public static void main(String[] args) throws Exception {
try (ExceptionTest exceptionTest = new ExceptionTest()) {
exceptionTest.open();
}
}
public void open() throws IOException {
System.out.println("invoke open method");
throw new IOException("open method exception");
}
public void close() throws Exception {
System.out.println("invoke close method");
throw new IOException("close method exception");
}
}
输出结果如下:
invoke open method
invoke close method
Exception in thread "main" java.io.IOException: open method exception
at com.demo.nio.ExceptionTest.open(ExceptionTest.java:31)
at com.demo.nio.ExceptionTest.main(ExceptionTest.java:8)
Suppressed: java.io.IOException: close method exception
at com.demo.nio.ExceptionTest.close(ExceptionTest.java:36)
at com.demo.nio.ExceptionTest.main(ExceptionTest.java:9)
从异常堆栈信息的对比很容易能看出来,使用try-catch-resource的方式不会丢失任何异常信息,并且这种语法也极大的化简了编码工作量。
3.2 Closeable
这是一个非常古老的接口,用于支持可显式关闭的资源,JDK1.5版本加入,1.7版本扩展了父接口AutoCloseable。
这意味着所有的通道都支持自动关闭和显式关闭。
3.3 Channel
这是真正意义上的通道接口,因为通道必须支持关闭操作,那么客户端就必须时刻掌握当前时刻通道是否可用,因此Channel接口中提供了isOpen函数。
2.4 InterruptibleChannel
这个接口什么方法都没留下,仅仅留下一个继承而来的close方法,然后补充了一些注释内容,从注释内容上看InterruptibleChannel表示一个可中断的通道,如果应用线程阻塞在此类型的通道上,那么当通道关闭时,会收到java.nio.channels.AsynchronousCloseException异常,以此来支持Java的中断模型。
实际上接口的实现远比接口的定义复杂:
- 如果当前线程在实现了此接口的通道上调用了阻塞方法,那么其他线程跳用此通道的close方法的时候,当前线程会收到AsynchronousCloseException;
- 如果当前线程在实现了此接口的通道上调用了阻塞方法,那么其他线程跳用此通道的interrupte方法时,通道会被关闭,当前线程也会收到AsynchronousCloseException,并且这个线程会一直处理中断状态,除非状态被重置
2.5 ReadableByteChannel
提供了从通道读取数据的方法,需要注意的是当前线程读取数据时,其他线程的读操作会被阻塞,并且涉及操作系统底层数据传输,因此仅支持字节读取。
2.6 ScatteringByteChannel
这个接口作为ReadableByteChannel的直接派生接口,扩展了字节读的操作,允许将通道中的数据读取到多个缓冲区中。
2.7 WritableByteChannel
和读相对,这是字节写入接口,和字节读相同的是当前线程写入时,其他线程的写操作会被阻塞。
2.8 GatheringByteChannel
和ScatteringByteChannel类似,允许多个缓冲区向通道中写入数据。
2.9 ByteChannel
这个接口从前文的接口关系UML图中可以看出,结合了字节读写接口,此接口的实现类可以具备读写操作。
2.10 SeekableByteChannel
这个接口派生自ByteChannel,在读写操作的基础上,扩充了对position的维护操作。
2.11 AsynchronousChannel
这是实现异步IO操作的核心接口,并且实现了此接口的通道是线程安全的,允许并发读写,但是需要注意的是不允许在一个IO未完成的情况下再次读写。
2.12 AsynchronousByteChannel
在AsynchronousChannel接口的基础上扩充了以字节为单位的读写操作。
2.13 NetworkChannel
有一个非常核心的接口,支持Socket的数据读写操作,将通道中的数据和Socket关联,网络NIO实现的最重要的接口。
2.14 MulticastChannel
这个接口稍微难理解一些,但是了解IP广播的话就不难了,它的功能就是支持IP多路广播的,将多个IP打包成一组,然后将报文向组内所有IP对应的主机发送。
四 结语
如果想关注更多硬技能的分享,可以参考积少成多系列传送门,未来每一篇关于硬技能的分享都会在传送门中更新链接。