小師妹學JavaIO之:NIO中Channel的妙用

簡介

小師妹,你還記得咱們使用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來運送數據。異步

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大類型,分別是:

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的博客

歡迎關注個人公衆號:程序那些事,更多精彩等着您!

相關文章
相關標籤/搜索