Java NIO5:通道和文件通道

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;

}
    和緩衝區不一樣,通道API主要由接口指定。不一樣的操做系統上通道實現會有根本性的差別,因此通道API僅僅描述了能夠作什麼,所以很天然地,通道實現常用操做系統的本地代碼,通道接口容許開發者以一種受控且可移植的方式來訪問底層的I/O服務。

    能夠從底層的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

相關文章
相關標籤/搜索