1、通道是什麼java
通道式(Channel)是java.nio的第二個主要創新。通道既不是一個擴展也不是一項加強,而是全新的、極好的Java I/O示例,提供與I/O服務的直接鏈接。Channel用於在字節緩衝區和位於通道另外一側的實體(一般是一個文件或套接字)之間有效地傳輸數據。安全
一般狀況下,通道與操做系統的文件描述符(FileDescriptor)和文件句柄(FileHandler)有着一對一的關係。雖然通道比文件描述符更廣義,但開發者常用到的多數通道都是鏈接到開放的文件描述符的。Channel類提供維持平臺獨立性所需的抽象過程,否則仍然會模擬現代操做系統自己的I/O性能。多線程
通道是一種途徑,藉助該途徑,能夠用最小的總開銷來訪問操做系統自己的I/O服務。緩衝區則是通道內部用來發送和接收數據的端點,以下圖:併發
2、通道基礎dom
首先,看一下基本的Channel接口,下面是Channel接口的完整源碼:socket
public interface Channel extends Closeable { /** * Tells whether or not this channel is open. </p> * * @return <tt>true</tt> if, and only if, this channel is open */ public boolean isOpen(); /** * Closes this channel. * * <p> After a channel is closed, any further attempt to invoke I/O * operations upon it will cause a {@link ClosedChannelException} to be * thrown. * * <p> If this channel is already closed then invoking this method has no * effect. * * <p> This method may be invoked at any time. If some other thread has * already invoked it, however, then another invocation will block until * the first invocation is complete, after which it will return without * effect. </p> * * @throws IOException If an I/O error occurs */ public void close() throws IOException; }
能夠從底層的Channel接口看到,對全部通道來講只有兩種共同的操做:檢查一個通道是否打開isOpen()和關閉一個打開的通道close(),其他全部的東西都是那些實現Channel接口以及它的子接口的類。性能
從Channel接口引伸出的其餘接口都是面向字節的子接口:this
包括WritableByteChannel和ReadableByteChannel。這也正好支持了咱們以前的所學:通道只能在字節緩衝區上操做。層次接口代表其餘數據類型的通道也能夠從Channel接口引伸而來。這是一種很好的鐳射機,不過非字節實現是不可能的,由於操做系統都是以字節的形式實現底層I/O接口的。spa
3、認識通道操作系統
看一下基本的接口:
public interface ReadableByteChannel extends Channel { public int read(ByteBuffer dst) throws IOException; }
public interface WritableByteChannel extends Channel { public int write(ByteBuffer src) throws IOException; }
public interface ByteChannel extends ReadableByteChannel, WritableByteChannel { }
通道能夠是單向的也能夠是雙向的。一個Channel類可能實現定義read()方法的ReadableByteChannel接口,而另外一個Channel類也許實現WritableByteChannel接口以提供write()方法。實現這兩種接口其中之一的類都是單向的,只能在一個方向上傳輸數據。若是一個類同時實現這兩個接口,那麼它是雙向的,能夠雙向傳輸數據,就像上面的ByteChannel。
通道能夠以阻塞(blocking)或非阻塞(nonblocking)模式運行,非阻塞模式的通道永遠不會讓調用的線程休眠,請求的操做要麼當即完成,要麼返回一個結果代表未進行任何操做。只有面向流的(stream-oriented)的通道,如sockets和pipes才能使用非阻塞模式。
比方說非阻塞的通道SocketChannel:
public abstract class SocketChannel extends AbstractSelectableChannel implements ByteChannel, ScatteringByteChannel, GatheringByteChannel { ... }
public abstract class AbstractSelectableChannel extends SelectableChannel { ... }
能夠看出,socket通道類從SelectableChannel類引伸而來,從SelectableChannel引伸而來的類能夠和支持有條件的選擇的選擇器(Selectors)一塊兒使用。將非阻塞I/O和選擇器組合起來可使開發者的程序利用多路複用I/O。
4、認識文件通道
通道是訪問I/O服務的導管,I/O能夠分爲廣義的兩大類:File I/O和Stream I/O。那麼相應的,通道也有兩種類型,它們是文件(File)通道和套接字(Socket)通道。文件通道指的是FileChannel,套接字通道則有三個,分別是SocketChannel、ServerSocketChannel和DatagramChannel。
通道能夠以多種方式建立。Socket通道能夠有直接建立Socket通道的工廠方法,可是一個FileChannel對象卻只能經過在一個打開的RandomAccessFile、FileInputStream或FileOutputStream對象上調用getChannel()方法來獲取,開發者不能直接建立一個FileChannel。
文件I/O是咱們最常使用的I/O,所以這部分先認識一下文件通道,下一部分再以代碼形式演示如何使用文件通道高。用UML圖表示一下文件通道的類層次關係:
文件通道老是阻塞式的,所以不能被置於非阻塞模式下。
前面提到過了,FileChannel對象不能直接建立,一個FileChannel實例只能經過在一個打開的File對象(RandomAccessFile、FileInputStream或FileOutputStream)上調用getChannel()方法獲取,調用getChannel()方法會返回一個鏈接到相同文件的FileChannel對象且該FileChannel對象具備與file對象相同的訪問權限,而後就可使用通道對象來利用強大的FileChannel API了。
FileChannel對象是線程安全的,多個進程能夠在同一個實例上併發調用方法而不會引發任何問題,不過並不是全部的操做都是多線程的。影響通道位置或者影響文件的操做都是單線程的,若是有一個線程已經在執行會影響通道位置或文件大小的操做,那麼其餘嘗試進行此類操做之一的線程必須等待,併發行爲也會受到底層操做系統或文件系統的影響。
5、使用文件通道讀數據
講了這麼多理論,下面來看一下如何使用文件通道,首先是從文件中讀出數據:
public static void main(String[] args) throws Exception { File file = new File("D:/files/readchannel.txt"); FileInputStream fis = new FileInputStream(file); FileChannel fc = fis.getChannel(); ByteBuffer bb = ByteBuffer.allocate(35); fc.read(bb); bb.flip(); while (bb.hasRemaining()) { System.out.print((char)bb.get()); } bb.clear(); fc.close(); }
這是最簡單的操做,前面講過文件通道必須經過一個打開的RandomAccessFile、FileInputStream、FileOutputStream獲取到,所以這裏使用FileInputStream來獲取FileChannel。接着只要使用read方法將內容讀取到緩衝區內便可,緩衝區內有了數據,就可使用前文對於緩衝區的操做讀取數據了。文件裏面的數據是:
控制檯上打印出來的數據是:
channel1! channel2! channel3!
沒有問題。
6、使用文件通道寫數據
上面看了使用文件通道讀數據,接着看一下使用文件通道寫數據,差很少:
public static void main(String[] args) throws Exception { File file = new File("D:/files/writechannel.txt"); RandomAccessFile raf = new RandomAccessFile(file, "rw"); FileChannel fc = raf.getChannel(); ByteBuffer bb = ByteBuffer.allocate(10); String str = "abcdefghij"; bb.put(str.getBytes()); bb.flip(); fc.write(bb); bb.clear(); fc.close(); }
這裏使用了RandomAccessFile去獲取FileChannel,而後操做其實差很少,write方法寫ByteBuffer中的內容至文件中,注意寫以前仍是要先把ByteBuffer給flip一下。
可能有人以爲這種連續put的方法很是不方便,可是沒有辦法,以前已經提到過了:通道只能使用ByteBuffer。