小师妹,你还记得咱们使用IO和NIO的初心吗?java
小师妹:F师兄,使用IO和NIO不就是为了让生活更美好,世界充满爱吗?让我等程序员能够优雅的将数据从一个地方搬运到另一个地方。利其器,善其事,才有更多的时间去享受生活呀。git
善,若是将数据比作人,IO,NIO的目的就是把人运到美国。程序员
小师妹:F师兄,为何要运到美国呀,美国如今新冠太严重了,仍是待在中国吧。中国是世界上最安全的国家!github
好吧,为了保险起见,咱们要把人运到上海。人就是数据,怎么运过去呢?能够坐飞机,坐汽车,坐火车,这些什么飞机,汽车,火车就能够看作是一个一个的Buffer。spring
更多精彩内容且看:安全
最后飞机的航线,汽车的公路和火车的轨道就能够看作是一个个的channel。服务器
简单点讲,channel就是负责运送Buffer的通道。dom
IO按源头来分,能够分为两种,从文件来的File IO,从Stream来的Stream IO。无论哪一种IO,均可以经过channel来运送数据。异步
虽然数据的来源只有两种,可是JDK中Channel的分类可很多,以下图所示:socket
先来看看最基本的,也是最顶层的接口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大类型,分别是:
这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;
在讲其余几个Channel以前,咱们看一个和下面几个channel相关的Selector:
这里要介绍一个新的Channel类型叫作SelectableChannel,以前的FileChannel的链接是一对一的,也就是说一个channel要对应一个处理的线程。而SelectableChannel则是一对多的,也就是说一个处理线程能够经过Selector来对应处理多个channel。
SelectableChannel经过注册不一样的SelectionKey,实现对多个Channel的监听。后面咱们会具体的讲解Selector的使用,敬请期待。
DatagramChannel是用来处理UDP的Channel。它自带了Open方法来建立实例。
来看看DatagramChannel的定义:
public abstract class DatagramChannel extends AbstractSelectableChannel implements ByteChannel, ScatteringByteChannel, GatheringByteChannel, MulticastChannel
ByteChannel表示它同时是ReadableByteChannel也是WritableByteChannel,能够同时写入和读取。
MulticastChannel表明的是一种多播协议。正好和UDP对应。
SocketChannel是用来处理TCP的channel。它也是经过Open方法来建立的。
public abstract class SocketChannel extends AbstractSelectableChannel implements ByteChannel, ScatteringByteChannel, GatheringByteChannel, NetworkChannel
SocketChannel跟DatagramChannel的惟一不一样之处就是实现的是NetworkChannel借口。
NetworkChannel提供了一些network socket的操做,好比绑定地址等。
ServerSocketChannel也是一个NetworkChannel,它主要用在服务器端的监听。
public abstract class ServerSocketChannel extends AbstractSelectableChannel implements NetworkChannel
最后AsynchronousSocketChannel是一种异步的Channel:
public abstract class AsynchronousSocketChannel implements AsynchronousByteChannel, NetworkChannel
为何是异步呢?咱们看一个方法:
public abstract Future<Integer> read(ByteBuffer dst);
能够看到返回值是一个Future,因此read方法能够马上返回,只在咱们须要的时候从Future中取值便可。
小师妹: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的博客
欢迎关注个人公众号:程序那些事,更多精彩等着您!