1. 程式人生 > >Java NIO 學習筆記(六)----非同步檔案通道 AsynchronousFileChannel

Java NIO 學習筆記(六)----非同步檔案通道 AsynchronousFileChannel

目錄:
Java NIO 學習筆記(一)----概述,Channel/Buffer
Java NIO 學習筆記(二)----聚集和分散,通道到通道
Java NIO 學習筆記(三)----Selector
Java NIO 學習筆記(四)----檔案通道和網路通道
Java NIO 學習筆記(五)----路徑、檔案和管道 Path/Files/Pipe
Java NIO 學習筆記(六)----非同步檔案通道 AsynchronousFileChannel

AsynchronousFileChannel 非同步檔案通道

在 Java 7 中,AsynchronousFileChannel 已新增到 Java NIO 中,它可以非同步讀取資料並將資料寫入檔案。先說明,非同步和阻塞/非阻塞沒有關係,下面簡單介紹一下相關概念:

  1. 阻塞是程序發起任務請求然後一直等到任務完成,再把任務結果返回。
  2. 非阻塞是發起任務請求之後就馬上返回去做別的事,然後再時不時主動檢視任務請求是否被完成。(輪詢)
  3. 同步是如果完成任務時,遇到一個耗時操作,需要等操作返回,才能繼續執行剩下的操作。
  4. 非同步是如果完成任務時,遇到一個耗時操作,不等完成,而是去做其他事情,也不主動檢視是否完成,而是由耗時操作完成的通知再回來繼續執行,常見的例子就是 Ajax 。

一般非同步都是和非阻塞組合使用的。

建立 AsynchronousFileChannel

通過其靜態方法 open() 建立 AsynchronousFileChannel ,下面是一個示例:

Path path = Paths.get("D:\\test\\input.txt");

AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(path, StandardOpenOption.READ);

// 作為對比,普通的 FileChannel 是這樣開啟的:
RandomAccessFile aFile = new RandomAccessFile("D:\\test\\input.txt", "rw");
FileChannel inChannel = aFile.getChannel();

open() 方法的第一個引數是指向 AsynchronousFileChannel 要關聯的檔案的 Path 例項。第二個引數是可選的,可以是一個或多個開啟選項,這些選項告訴 AsynchronousFileChannel 在底層檔案上執行什麼操作。在這個例子中,我們使用了 StandardOpenOption.READ,這意味著該檔案將被開啟以供閱讀。

讀取資料

可以通過兩種方式從 AsynchronousFileChannel 讀取資料,都是呼叫 read() 方法之一完成的。

Future<Integer> read(ByteBuffer dst, long position);

<A> void read(ByteBuffer dst,
                                  long position,
                                  A attachment,
                                  CompletionHandler<Integer,? super A> handler);

通過 Future 讀取資料

Future 是 Java 多執行緒方面的內容,後面我會再繼續學習多執行緒的知識,這裡先稍微瞭解,帶過。
首先這個 Futrue 類有什麼用?我們知道多執行緒程式設計的時候,一般使用 Runable ,重寫 run() 方法,然後呼叫執行緒物件的 start() 方法,重點就在 run() 方法的返回值是 void ,那如果我們需要執行緒執行完成後返回結果怎麼辦,Future 就可以解決這個問題。

從 AsynchronousFileChannel 讀取資料的第一種方法是呼叫 read() 方法,該方法返回 Future :

Future<Integer> operation = fileChannel.read(buffer, 0);

此版本的 read() 方法將 ByteBuffer 作為第一個引數。 從 AsynchronousFileChannel 讀取的資料被讀入此 ByteBuffer ,第二個引數指定檔案中要開始讀取的位元組位置。

即使讀取操作尚未完成,read() 方法也會立即返回,可以通過呼叫 read() 方法返回的 Future 例項的 isDone() 方法來檢查讀取操作何時完成。這是一個示例:

Path path = Paths.get("D:\\test\\input.txt");
AsynchronousFileChannel channel = AsynchronousFileChannel.open(path, StandardOpenOption.READ);

ByteBuffer buffer = ByteBuffer.allocate(1024);
long position = 0;
Future<Integer> operation = channel.read(buffer, position);

while (!operation.isDone()) ;

buffer.flip();
System.out.println(new String(buffer.array()));

此示例建立一個 AsynchronousFileChannel ,然後建立一個 ByteBuffer ,它作為引數傳遞給 read() 方法,並且位置為 0 ,在呼叫 read() 之後,迴圈呼叫 Future 的 isDone() 方法直到返回 true。 當然,這不是一個非常有效的 CPU 使用,但是這裡需要等待讀取操作完成。讀取操作完成後,將資料讀入 ByteBuffer 並輸出。

通過 CompletionHandler 讀取資料

從 AsynchronousFileChannel 讀取資料的第二種方法是呼叫以 CompletionHandler 作為引數的 read() 方法版本,其第二個引數 position 可指定從什麼位置開始讀取。 以下是呼叫此 read() 方法的示例:

Path path = Paths.get("D:\\test\\input.txt");
AsynchronousFileChannel channel = AsynchronousFileChannel.open(path);

ByteBuffer buffer = ByteBuffer.allocate(1024);
channel.read(buffer, 0, buffer, new CompletionHandler<Integer, ByteBuffer>() {
    @Override
    public void completed(Integer result, ByteBuffer attachment) {
        System.out.println("result: " + result);
        attachment.flip();
        System.out.println(new String(attachment.array()));
        attachment.clear();
    }

    @Override
    public void failed(Throwable exc, ByteBuffer attachment) {
        System.out.println("failed");
    }
});

一旦讀取操作完成,將呼叫 CompletionHandler 的 completed() 方法,completed() 方法的第一個引數是 Integer 型別,表示讀取了多少位元組,attachment 對應 read() 方法的第三個引數。 在這個例子中也是 ByteBuffer,資料也被讀入其中。 可以自由選擇要附加的物件。如果讀取操作失敗,則將呼叫 CompletionHandler 的 failed() 方法。

寫入資料

和讀取資料類似,也可以通過 AsynchronousFileChannel 物件的 2 種方法寫入資料:

Future<Integer> write(ByteBuffer src, long position);

<A> void write(ByteBuffer src,
                                   long position,
                                   A attachment,
                                   CompletionHandler<Integer,? super A> handler);

具體的操作請參考讀取資料,這裡不展開了。