1. 程式人生 > >Java NIO系列七之 AsynchronousFileChannel非同步檔案通道

Java NIO系列七之 AsynchronousFileChannel非同步檔案通道

Java7中新增了AsynchronousFileChannel作為nio的一部分。AsynchronousFileChannel使得資料可以進行非同步讀寫。下面將介紹一下AsynchronousFileChannel的使用。

建立AsynchronousFileChannel(Creating an AsynchronousFileChannel)

AsynchronousFileChannel的建立可以通過open()靜態方法:

Path path = Paths.get("data/test.xml");

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

open()的第一個引數是一個Path實體,指向我們需要操作的檔案。 第二個引數是操作型別。上述示例中我們用的是StandardOpenOption.READ,表示以讀的形式操作檔案。

讀取資料(Reading Data)

讀取AsynchronousFileChannel的資料有兩種方式。每種方法都會呼叫AsynchronousFileChannel的一個read()介面。下面分別看一下這兩種寫法。

通過Future讀取資料(Reading Data Via a Future)

第一種方式是呼叫返回值為Future的read()方法:

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

這種方式中,read()接受一個ByteBuffer座位第一個引數,資料會被讀取到ByteBuffer中。第二個引數是開始讀取資料的位置。

read()方法會立刻返回,即使讀操作沒有完成。我們可以通過isDone()方法檢查操作是否完成。

下面是一個略長的示例:

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

ByteBuffer buffer = ByteBuffer.allocate(1024);
long position = 0;

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

while(!operation.isDone());

buffer.flip();
byte[] data = new byte[buffer.limit()];
buffer.get(data);
System.out.println(new String(data));
buffer.clear();

在這個例子中我們建立了一個AsynchronousFileChannel,然後建立一個ByteBuffer作為引數傳給read。接著我們建立了一個迴圈來檢查是否讀取完畢isDone()。這裡的迴圈操作比較低效,它的意思是我們需要等待讀取動作完成。

一旦讀取完成後,我們就可以把資料寫入ByteBuffer,然後輸出。

通過CompletionHandler讀取資料(Reading Data Via a CompletionHandler)

另一種方式是呼叫接收CompletionHandler作為引數的read()方法。下面是具體的使用:

fileChannel.read(buffer, position, buffer, new CompletionHandler<Integer, ByteBuffer>() {
    @Override
    public void completed(Integer result, ByteBuffer attachment) {
        System.out.println("result = " + result);

        attachment.flip();
        byte[] data = new byte[attachment.limit()];
        attachment.get(data);
        System.out.println(new String(data));
        attachment.clear();
    }

    @Override
    public void failed(Throwable exc, ByteBuffer attachment) {

    }
});

這裡,一旦讀取完成,將會觸發CompletionHandler的completed()方法,並傳入一個Integer和ByteBuffer。前面的整形表示的是讀取到的位元組數大小。第二個ByteBuffer也可以換成其他合適的物件方便資料寫入。 如果讀取操作失敗了,那麼會觸發failed()方法。

寫資料(Writing Data)

和讀資料類似某些資料也有兩種方式,調動不同的的write()方法,下面分別看介紹這兩種方法。

通過Future寫資料(Writing Data Via a Future)

通過AsynchronousFileChannel我們可以一步寫資料

Path path = Paths.get("data/test-write.txt");
AsynchronousFileChannel fileChannel = 
    AsynchronousFileChannel.open(path, StandardOpenOption.WRITE);

ByteBuffer buffer = ByteBuffer.allocate(1024);
long position = 0;

buffer.put("test data".getBytes());
buffer.flip();

Future<Integer> operation = fileChannel.write(buffer, position);
buffer.clear();

while(!operation.isDone());

System.out.println("Write done");

首先把檔案已寫方式開啟,接著建立一個ByteBuffer座位寫入資料的目的地。再把資料進入ByteBuffer。最後檢查一下是否寫入完成。 需要注意的是,這裡的檔案必須是已經存在的,否者在嘗試write資料是會丟擲一個java.nio.file.NoSuchFileException.

檢查一個檔案是否存在可以通過下面的方法:

if(!Files.exists(path)){
    Files.createFile(path);
}

通過CompletionHandler寫資料(Writing Data Via a CompletionHandler)

我們也可以通過CompletionHandler來寫資料:

Path path = Paths.get("data/test-write.txt");
if(!Files.exists(path)){
    Files.createFile(path);
}
AsynchronousFileChannel fileChannel = 
    AsynchronousFileChannel.open(path, StandardOpenOption.WRITE);

ByteBuffer buffer = ByteBuffer.allocate(1024);
long position = 0;

buffer.put("test data".getBytes());
buffer.flip();

fileChannel.write(buffer, position, buffer, new CompletionHandler<Integer, ByteBuffer>() {

    @Override
    public void completed(Integer result, ByteBuffer attachment) {
        System.out.println("bytes written: " + result);
    }

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

同樣當資料吸入完成後completed()會被呼叫,如果失敗了那麼failed()會被呼叫。