小師妹,你還記得咱們使用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的博客
歡迎關注個人公衆號:程序那些事,更多精彩等着您!