前面一些Java的文章已經(jīng)簡(jiǎn)要對Java NIO做了描述,也詳細地介紹了NIO中最基本的Buffer。這篇文章將繼續介紹NIO的第二個(gè)主要角色 —— Channel。Channel是對數據的源頭和數據目標點(diǎn)流經(jīng)途徑的抽象,在這個(gè)意義上和InputStream和OutputStream類(lèi)似。Channel可以譯為“通道、管道”,而傳輸中的數據仿佛就像是在其中流淌的水。前面也提到了Buffer,Buffer和Channel相互配合使用,才是Java的NIO。
0. Channel簡(jiǎn)述
“A nexus for I/O operations.”這是Java API中對Channel的描述,說(shuō)的是Channel是IO操作的連接點(diǎn)。
開(kāi)門(mén)見(jiàn)山,既然Channel是Java NIO中這么重要的一個(gè)角色,那么我們來(lái)看一下Java API中給出的類(lèi)結構圖。
Channel類(lèi)層次結構(摘自《Java NIO》)
從上至下,前三層是描述和約定Channel功能的interface(除了java.nio.channels.spi. AbstractInterruptibleChannel),而在下面幾層是和這些結構對應的Channel類(lèi)。
圖中也給出了各個(gè)interface和class的方法。Channel接口本身只是總體上提供了兩個(gè)“不痛不癢”的方法。而緊接著(zhù)的下面三個(gè)接口,各為自己的特征給出了一個(gè)方法約定。第三層的接口則是更進(jìn)一步更具體的擴展。
再往下看,就是實(shí)現這些接口的類(lèi),從圖中左右兩側可以看到,實(shí)際上是分為了兩大類(lèi)。一類(lèi)是SelectableChannel,而另一類(lèi)是FileChannel。而SelectableChannel實(shí)際上是網(wǎng)絡(luò )通信相關(guān)的SocketChannel等的實(shí)現基礎。
需要注意的是,圖中下層的這些class實(shí)際上也都是abstract的,即抽象類(lèi)。我們上面也說(shuō)過(guò),Channel是數據源和數據歸宿之間通道的抽象。而實(shí)際的IO必然是和底層操作系統相關(guān)的,是需要依賴(lài)平臺的具體實(shí)現,這些不會(huì )再高度抽象的跨平臺Java API中體現,只是在JDK實(shí)現中有所不同。
除圖中提到的以外,還有提供靜態(tài)方法的工具類(lèi)Channels。另外,后續的JDK1.7中頁(yè)增加了新的內容。
1. Channel的使用和分類(lèi)
之前在介紹ByteBuffer的時(shí)候就已經(jīng)提到過(guò)了,Channel是同系統底層數據的交互,為了保證效率,統一使用字節為單位。從上面的類(lèi)層次結構圖中也可以看得出來(lái),Channel的兩個(gè)子接口分別是ReadableByteChannel和WritableByteChannel,而B(niǎo)yteChannel則對這兩個(gè)接口做了合并歸納。而且SocketChannel和FileChannel都實(shí)現了這些接口。
需要注意的是,隨著(zhù)Java API的發(fā)展變化,接口的繼承關(guān)系也和上圖繪制的有所不同。如在JavaSE7中,增加了SeekableByteChannel接口等。盡管如此,大體上還是維持了原來(lái)的設計結構。
關(guān)于Channel對象的創(chuàng )建,有兩類(lèi)情況。對于Socket相關(guān)的Channel,只要調用特定類(lèi)的open()方法即可,之后進(jìn)行bind或者connect操作。而文件相關(guān)的FileChannel則不然,需要通過(guò)FileInputStream、FileOutputStream或者RandomAccessFile的getChannel()獲取Channel對象。
1
2
3
4
5
6
7
8
9
10
SocketChannel sc = SocketChannel.open( );
sc.connect (new InetSocketAddress ("somehost", someport));
ServerSocketChannel ssc = ServerSocketChannel.open( );
ssc.socket( ).bind (new InetSocketAddress (somelocalport));
DatagramChannel dc = DatagramChannel.open( );
RandomAccessFile raf = new RandomAccessFile ("somefile", "r");
FileChannel fc = raf.getChannel( );
從這個(gè)地方,我們就發(fā)現,其實(shí)Channel根據IO服務(wù)的情況主要分為兩大類(lèi),按照《Java NIO》的描述,兩類(lèi)IO分別是:file I/O 和 stream I/O。前者是針對文件讀寫(xiě)操作的,而后者多是網(wǎng)絡(luò )通信相關(guān)的和Socket相關(guān)的。Channel分類(lèi)也基本如此,和前者對應的FileChannel,以及與后者對應的SocketChannel等類(lèi)對象。
兩者的區別不僅僅在Channel對象的構建和初始化上,更重要的還是和Selecotor的結合使用相關(guān)。也包括是否支持阻塞模式等。
接下來(lái)就是通過(guò)Channel進(jìn)行實(shí)際的IO操作,即讀和寫(xiě)。其實(shí),這兩項分別都在ReadableByteChannel和WritableByteChannel中聲明了的。ByteChannel的子類(lèi)都有
1
2
public int read (ByteBuffer dst) throws IOException;
public int write (ByteBuffer src) throws IOException;
這兩個(gè)方法。
需要注意的是,盡管你所拿到的Channel對象都是有read()和write()方法的,但并不代表他們一定是可讀或者可寫(xiě)的。是否可以讀寫(xiě)需要了解這個(gè)Channel的來(lái)頭,這是比較討厭的一點(diǎn)。
讀寫(xiě)之后,通常就要進(jìn)行關(guān)閉操作。關(guān)閉的概念其實(shí)很簡(jiǎn)單,當一個(gè)Channel被關(guān)閉后,就不再可用。相關(guān)的close()和isOpen()方法可以進(jìn)行關(guān)閉和檢查Channel是否是打開(kāi)狀態(tài)。執行close()可能會(huì )對線(xiàn)程造成阻塞,當一個(gè)Channel關(guān)閉后,后續的close()操作直接返回。
當Channel對象的close和中斷結合起來(lái),就有些要提到的了。一個(gè)阻塞在Channel對象上的線(xiàn)程如果收到了中斷操作,則Channel對象關(guān)閉,線(xiàn)程收到ClosedByInterruptException異常。而把順序稍微調整一下,一個(gè)已經(jīng)被set中斷信號的線(xiàn)程訪(fǎng)問(wèn)Channel對象時(shí),Channel對象也會(huì )被關(guān)閉,線(xiàn)程得到ClosedByInterruptException異常。其它阻塞在這個(gè)Channel上的線(xiàn)程會(huì )得到AsynchronousCloseException異常。
以上這段中提到的Channel對象需要InterruptibleChannel接口,但FileChannel和SocketChannel等大多數類(lèi)都完成了對這個(gè)interface的實(shí)現。
2. Scatter/Gather集與散
Scatter的意思是分散,Gather的意思是聚集。我們注意到在上面的類(lèi)層次結構圖中,除了ByteChannel外,各Channel類(lèi)還都實(shí)現了兩個(gè)接口,分別是:
ScatteringByteChannel
GatheringByteChannel
對于這兩個(gè)接口,這里不做過(guò)多描述。也許我們通過(guò)接口中約定的方法,就大概知道他們的作用了。
1
2
3
4
5
6
7
8
9
10
public interface ScatteringByteChannel extends ReadableByteChannel
{
public long read (ByteBuffer [] dsts) throws IOException;
public long read (ByteBuffer [] dsts, int offset, int length) throws IOException;
}
public interface GatheringByteChannel extends WritableByteChannel
{
public long write(ByteBuffer[] srcs) throws IOException;
public long write(ByteBuffer[] srcs, int offset, int length) throws IOException;
}
實(shí)際上就是對讀寫(xiě)操作進(jìn)行順序多Buffer的支持。