简介
小师妹,你还记得我们使用 IO 和 NIO 的初心吗?
小师妹:F 师兄,使用 IO 和 NIO 不就是为了让生活更美好,世界充满爱吗?让我等程序员可以优雅的将数据从一个地方搬运到另外一个地方。利其器,善其事,才有更多的时间去享受生活呀。
善,如果将数据比做人,IO,NIO 的目的就是把人运到美国。
小师妹:F 师兄,为什么要运到美国呀,美国现在新冠太严重了,还是待在中国吧。中国是世界上最安全的国家!
好吧,为了保险起见,我们要把人运到上海。人就是数据,怎么运过去呢?可以坐飞机,坐汽车,坐火车,这些什么飞机,汽车,火车就可以看做是一个一个的 Buffer。
更多精彩内容且看:
- 区块链从入门到放弃系列教程 - 涵盖密码学, 超级账本, 以太坊,Libra, 比特币等持续更新
- Spring Boot 2.X 系列教程: 七天从无到有掌握 Spring Boot- 持续更新
- Spring 5.X 系列教程: 满足你对 Spring5 的一切想象 - 持续更新
- java 程序员从小工到专家成神之路(2020 版)- 持续更新中, 附详细文章教程
最后飞机的航线,汽车的公路和火车的轨道就可以看做是一个个的 channel。
简单点讲,channel 就是负责运送 Buffer 的通道。
IO 按源头来分,可以分为两种,从文件来的 File IO,从 Stream 来的 Stream IO。不管哪种 IO,都可以通过 channel 来运送数据。
Channel 的分类
虽然数据的来源只有两种,但是 JDK 中 Channel 的分类可不少,如下图所示:
先来看看最基本的,也是最顶层的接口 Channel:
public interface Channel extends Closeable {public boolean isOpen();
public void close() throws IOException;}
最顶层的 Channel 很简单,继承了 Closeable 接口,需要实现两个方法 isOpen 和 close。
一个用来判断 channel 是否打开,一个用来关闭 channel。
小师妹:F 师兄,顶层的 Channel 怎么这么简单,完全不符合 Channel 很复杂的人设啊。
别急,JDK 这么做其实也是有道理的,因为是顶层的接口,必须要更加抽象更加通用,结果,一通用就发现还真的就只有这么两个方法是通用的。
所以为了应对这个问题,Channel 中定义了很多种不同的类型。
最最底层的 Channel 有 5 大类型,分别是:
FileChannel
这 5 大 channel 中,和文件 File 有关的就是这个 FileChannel 了。
FileChannel 可以从 RandomAccessFile, FileInputStream 或者 FileOutputStream 中通过调用 getChannel() 来得到。
也可以直接调用 FileChannel 中的 open 方法传入 Path 创建。
public abstract class FileChannel
extends AbstractInterruptibleChannel
implements SeekableByteChannel, GatheringByteChannel, ScatteringByteChannel
我们看下 FileChannel 继承或者实现的接口和类。
AbstractInterruptibleChannel 实现了 InterruptibleChannel 接口,interrupt 大家都知道吧,用来中断线程执行的利器。来看一下下面一段非常玄妙的代码:
protected final void begin() {if (interruptor == null) {interruptor = new Interruptible() {public void interrupt(Thread target) {synchronized (closeLock) {if (closed)
return;
closed = true;
interrupted = target;
try {AbstractInterruptibleChannel.this.implCloseChannel();
} catch (IOException x) {}}
}};
}
blockedOn(interruptor);
Thread me = Thread.currentThread();
if (me.isInterrupted())
interruptor.interrupt(me);
}
上面这段代码就是 AbstractInterruptibleChannel 的核心所在。
首先定义了一个 Interruptible 的实例,这个实例中有一个 interrupt 方法,用来关闭 Channel。
然后获得当前线程的实例,判断当前线程是否 Interrupted,如果是的话,就调用 Interruptible 的 interrupt 方法将当前 channel 关闭。
SeekableByteChannel 用来连接 Entry 或者 File。它有一个独特的属性叫做 position,表示当前读取的位置。可以被修改。
GatheringByteChannel 和 ScatteringByteChannel 表示可以一次读写一个 Buffer 序列结合(Buffer Array):
public long write(ByteBuffer[] srcs, int offset, int length)
throws IOException;
public long read(ByteBuffer[] dsts, int offset, int length)
throws IOException;
Selector 和 Channel
在讲其他几个 Channel 之前,我们看一个和下面几个 channel 相关的 Selector:
这里要介绍一个新的 Channel 类型叫做 SelectableChannel,之前的 FileChannel 的连接是一对一的,也就是说一个 channel 要对应一个处理的线程。而 SelectableChannel 则是一对多的,也就是说一个处理线程可以通过 Selector 来对应处理多个 channel。
SelectableChannel 通过注册不同的 SelectionKey,实现对多个 Channel 的监听。后面我们会具体的讲解 Selector 的使用,敬请期待。
DatagramChannel
DatagramChannel 是用来处理 UDP 的 Channel。它自带了 Open 方法来创建实例。
来看看 DatagramChannel 的定义:
public abstract class DatagramChannel
extends AbstractSelectableChannel
implements ByteChannel, ScatteringByteChannel, GatheringByteChannel, MulticastChannel
ByteChannel 表示它同时是 ReadableByteChannel 也是 WritableByteChannel,可以同时写入和读取。
MulticastChannel 代表的是一种多播协议。正好和 UDP 对应。
SocketChannel
SocketChannel 是用来处理 TCP 的 channel。它也是通过 Open 方法来创建的。
public abstract class SocketChannel
extends AbstractSelectableChannel
implements ByteChannel, ScatteringByteChannel, GatheringByteChannel, NetworkChannel
SocketChannel 跟 DatagramChannel 的唯一不同之处就是实现的是 NetworkChannel 借口。
NetworkChannel 提供了一些 network socket 的操作,比如绑定地址等。
ServerSocketChannel
ServerSocketChannel 也是一个 NetworkChannel,它主要用在服务器端的监听。
public abstract class ServerSocketChannel
extends AbstractSelectableChannel
implements NetworkChannel
AsynchronousSocketChannel
最后 AsynchronousSocketChannel 是一种异步的 Channel:
public abstract class AsynchronousSocketChannel
implements AsynchronousByteChannel, NetworkChannel
为什么是异步呢?我们看一个方法:
public abstract Future<Integer> read(ByteBuffer dst);
可以看到返回值是一个 Future,所以 read 方法可以立刻返回,只在我们需要的时候从 Future 中取值即可。
使用 Channel
小师妹:F 师兄,讲了这么多种类的 Channel,看得我眼花缭乱,能不能讲一个 Channel 的具体例子呢?
好的小师妹,我们现在讲一个使用 Channel 进行文件拷贝的例子,虽然 Channel 提供了 transferTo 的方法可以非常简单的进行拷贝,但是为了能够看清楚 Channel 的通用使用,我们选择一个更加常规的例子:
public void useChannelCopy() throws IOException {FileInputStream input = new FileInputStream ("src/main/resources/www.flydean.com");
FileOutputStream output = new FileOutputStream ("src/main/resources/www.flydean.com.txt");
try(ReadableByteChannel source = input.getChannel(); WritableByteChannel dest = output.getChannel()){ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
while (source.read(buffer) != -1)
{
// flip buffer, 准备写入
buffer.flip();
// 查看是否有更多的内容
while (buffer.hasRemaining())
{dest.write(buffer);
}
// clear buffer,供下一次使用
buffer.clear();}
}
}
上面的例子中我们从 InputStream 中读取 Buffer,然后写入到 FileOutputStream。
总结
今天讲解了 Channel 的具体分类,和一个简单的例子,后面我们会再体验一下 Channel 的其他例子,敬请期待。
本文的例子 https://github.com/ddean2009/learn-java-io-nio
本文作者:flydean 程序那些事
本文链接:http://www.flydean.com/java-io-nio-channel/
本文来源:flydean 的博客
欢迎关注我的公众号: 程序那些事,更多精彩等着您!