Java NIO AsynchronousFileChannel
Jakob Jenkov |
In Java 7 the AsynchronousFileChannel
was added to Java NIO. The AsynchronousFileChannel
makes it possible to read data from, and write data to files asynchronously. This tutorial will explain how to
use the AsynchronousFileChannel
.
Creating an AsynchronousFileChannel
You create an AsynchronousFileChannel
via its static method open()
. Here is an example
of creating an AsynchronousFileChannel
:
Path path = Paths.get("data/test.xml"); AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(path, StandardOpenOption.READ);
The first parameter to the open()
method is a Path instance pointing to the file
the AsynchronousFileChannel
is to be associated with.
The second parameter is one or more open options which tell the AsynchronousFileChannel
what
operations is to be performed on the underlying file. In this example we used the StandardOpenOption.READ
which means that the file will be opened for reading.
Reading Data
You can read data from an AsynchronousFileChannel
in two ways. Each way to read data call one of the
read()
methods of the AsynchronousFileChannel
. Both methods of reading data will be covered
in the following sections.
Reading Data Via a Future
The first way to read data from an AsynchronousFileChannel
is to call the read()
method
that returns a Future
. Here is how calling that read()
method looks:
Future<Integer> operation = fileChannel.read(buffer, 0);
This version of the read()
method takes ByteBuffer
as first parameter. The data read
from the AsynchronousFileChannel
is read into this ByteBuffer
. The second parameter is
the byte position in the file to start reading from.
The read()
method return immediately, even if the read operation has not finished.
You can check the when the read operation is finished by calling the isDone()
method of the
Future
instance returned by the read()
method.
Here is a longer example showing how to use this version of the read()
method:
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();
This example creates an AsynchronousFileChannel
and then creates a ByteBuffer
which is
passed to the read()
method as parameter, along with a position of 0. After calling read()
the example loops until the isDone()
method of the returned Future
returns true. Of
course, this is not a very efficient use of the CPU - but somehow you need to wait until the read operation has
completed.
Once the read operation has completed the data read into the ByteBuffer
and then into a String and
printed to System.out
.
Reading Data Via a CompletionHandler
The second method of reading data from an AsynchronousFileChannel
is to call the read()
method version that takes a CompletionHandler
as a parameter. Here is how you call this read()
method:
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) { } });
Once the read operation finishes the CompletionHandler
's completed()
method will be
called. As parameters to the completed()
method are passed an Integer
telling how
many bytes were read, and the "attachment" which was passed to the read()
method. The "attachment"
is the third parameter to the read()
method. In this case it was the ByteBuffer
into
which the data is also read. You can choose freely what object to attach.
If the read operation fails, the failed()
method of the CompletionHandler
will get called
instead.
Writing Data
Just like with reading, you can write data to an AsynchronousFileChannel
in two ways. Each way to write
data call one of the write()
methods of the AsynchronousFileChannel
. Both methods of writing data will be covered
in the following sections.
Writing Data Via a Future
The AsynchronousFileChannel
also enables you to write data asynchronously. Here is a full
Java AsynchronousFileChannel
write example:
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");
First an AsynchronousFileChannel
is opened in write mode. Then a ByteBuffer
is created
and some data written into it. Then the data in the ByteBuffer
is written to the file. Finally
the example checks the returned Future
to see when the write operation has completed.
Note, that the file must already exist before this code will work. If the file does not exist the write()
method will throw a java.nio.file.NoSuchFileException
.
You can make sure that the file the Path
points to exists with the following code:
if(!Files.exists(path)){ Files.createFile(path); }
Writing Data Via a CompletionHandler
You can also write data to the AsynchronousFileChannel
with a CompletionHandler
to tell you
when the write is complete instead of a Future
. Here is an example of writing data to the
AsynchronousFileChannel
with a 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(); } });
The CompletionHandler
's completed()
method will get called when the write operation
completes. If the write fails for some reason, the failed()
method will get called instead.
Notice how the ByteBuffer
is used as attachment - the object which is passed on to the
CompletionHandler
's methods.
Tweet | |
Jakob Jenkov |