Part 1: The asynchronous channel APIs
The More New I/O APIs for the Java™ Platform (NIO.2) is one of the major new functional areas in Java 7, adding asynchronous channel functionality and a new file system API to the language. Developers will gain support for platform-independent file operations, asynchronous operations, and multicast socket channels. Part 1 of this two-part article focuses on the asynchronous channel APIs in NIO.2, and Part 2 covers the new file system functionality.
An asynchronous channel represents a connection that supports nonblocking operations, such as connecting, reading, and writing, and provides mechanisms for controlling the operations after they've been initiated. The More New I/O APIs for the Java Platform (NIO.2) in Java 7 enhance the New I/O APIs (NIO) introduced in Java 1.4 by adding four asynchronous channels to the java.nio.channels
package:
AsynchronousSocketChannel
AsynchronousServerSocketChannel
AsynchronousFileChannel
AsynchronousDatagramChannel
These classes are similar in style to the NIO channel APIs. They share the same method and argument structures, and most operations available to the NIO channel classes are also available in the new asynchronous versions. The main difference is that the new channels enable some operations to be executed asynchronously.
The asynchronous channel APIs provide two mechanisms for monitoring and controlling the initiated asynchronous operations. The first is by returning a java.util.concurrent.Future
object, which models a pending operation and can be used to query its state and obtain the result. The second is by passing to the operation an object of a new class, java.nio.channels.CompletionHandler
, which defines handler methods that are executed after the operation has completed. Each asynchronous channel class defines duplicate API methods for each operation so that either mechanism can be used.
This article, the first in a two-part series on NIO.2, introduces each of the channels and provides some simple examples to demonstrate their use. The examples are available in a runnable state (see Download), and you can try them out on the Java 7 beta releases available from Oracle and IBM® (both still under development at the time of this writing; see Resources). In Part 2, you'll learn about the NIO.2 file system API.
Asynchronous socket channels and futures
To start, we'll look at the AsynchronousServerSocketChannel
and AsynchronousSocketChannel
classes. Our first example demonstrates how a simple client/server can be implemented using these new classes. First we'll set up the server.
Server setup
An AsychronousServerSocketChannel
can be opened and bound to an address similarly to a ServerSocketChannel
:
AsynchronousServerSocketChannel server =
AsynchronousServerSocketChannel.open().bind(null);
The bind()
method takes a socket address as its argument. A convenient way to find a free port is to pass in a null
address, which automatically binds the socket to the local host address and uses a free ephemeral port.
Next, we can tell the channel to accept a connection:
Future<AsynchronousSocketChannel> acceptFuture = server.accept();
This is the first difference from NIO. The accept call always returns immediately, and — unlike ServerSocketChannel.accept()
, which returns a SocketChannel
— it returns a Future<AsynchronousSocketChannel>
object that can be used to retrieve an AsynchronousSocketChannel
at a later time. The generic type of the Future
object is the result of the actual operation. For example, a read or write returns aFuture<Integer>
because the operation returns the number of bytes read or written.
Using the Future
object, the current thread can block to wait for the result:
AsynchronousSocketChannel worker = future.get();
Here it blocks with a timeout of 10 seconds:
AsynchronousSocketChannel worker = future.get(10, TimeUnit.SECONDS);
Or it can poll the current state of the operation, and also cancel the operation:
if (!future.isDone()) {
future.cancel(true);
}
The cancel()
method takes a boolean flag to indicate whether the thread performing the accept can be interrupted. This is a useful enhancement; in previous Java releases, blocking I/O operations like this could only be aborted by closing the socket.
Client setup
Next, we can set up the client by opening and connecting a AsynchronousSocketChannel
to the server:
AsynchronousSocketChannel client = AsynchronousSocketChannel.open();
client.connect(server.getLocalAddress()).get();
Once the client is connected to the server, reads and writes can be performed via the channels using byte buffers, as shown in Listing 1:
Listing 1. Using byte buffers for reads and writes
// send a message to the server
ByteBuffer message = ByteBuffer.wrap("ping".getBytes());
client.write(message).get(); // read a message from the client
worker.read(readBuffer).get(10, TimeUnit.SECONDS);
System.out.println("Message: " + new String(readBuffer.array()));
Scattering reads and writes, which take an array of byte buffers, are also supported asynchronously.
The APIs of the new asynchronous channels completely abstract away from the underlying sockets: there's no way to obtain the socket directly, whereas previously you could call socket()
on, for example, a SocketChannel
. Two new methods — getOption
and setOption
— have been introduced for querying and setting socket options in the asynchronous network channels. For example, the receive buffer size can be retrieved by channel.getOption(StandardSocketOption.SO_RCVBUF)
instead of channel.socket().getReceiveBufferSize();
.
Completion handlers
The alternative mechanism to using Future
objects is to register a callback to the asynchronous operation. The CompletionHandler
interface has two methods:
-
void completed(V result, A attachment)
executes if a task completes with a result of typeV
. -
void failed(Throwable e, A attachment)
executes if the task fails to complete due toThrowable e
.
The attachment parameter of both methods is an object that is passed in to the asynchronous operation. It can be used to track which operation finished if the same completion-handler object is used for multiple operations.
Open commands
Let's look at an example using the AsynchronousFileChannel
class. We can create a new channel by passing in a java.nio.file.Path
object to the static open()
method:
AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(Paths.get("myfile"));
New open
commands for FileChannel
The format of the open commands for asynchronous channels has been backported to the FileChannel
class. Under NIO, a FileChannel
is obtained by calling getChannel()
on a FileInputStream
,FileOutputStream
, or RandomAccessFile
. With NIO.2, a FileChannel
can be created directly using anopen()
method, as in the examples shown here.
Path
is a new class in Java 7 that we look at in more detail in Part 2. We use thePaths.get(String)
utility method to create a Path
from a String
representing the filename.
By default, the file is opened for reading. The open()
method can take additional options to specify how the file is opened. For example, this call opens a file for reading and writing, creates it if necessary, and tries to delete it when the channel is closed or when the JVM terminates:
fileChannel = AsynchronousFileChannel.open(Paths.get("afile"),
StandardOpenOption.READ, StandardOpenOption.WRITE,
StandardOpenOption.CREATE, StandardOpenOption.DELETE_ON_CLOSE);
An alternative open()
method provides finer control over the channel, allowing file attributes to be set.
Implementing a completion handler
Next, we want to write to the file and then, once the write has completed, execute something. We first construct a CompletionHandler
that encapsulates the "something" as shown in Listing 2:
Listing 2. Creating a completion handler
CompletionHandler<Integer, Object> handler =
new CompletionHandler<Integer, Object>() {
@Override
public void completed(Integer result, Object attachment) {
System.out.println(attachment + " completed with " + result + " bytes written");
}
@Override
public void failed(Throwable e, Object attachment) {
System.err.println(attachment + " failed with:");
e.printStackTrace();
}
};
Now we can perform the write:
fileChannel.write(ByteBuffer.wrap(bytes), 0, "Write operation 1", handler);
The write()
method takes:
- A
ByteBuffer
containing the contents to write - An absolute position in the file
- An attachment object that is passed on to the completion handler methods
- A completion handler
Operations must give an absolute position in the file to read to or write from. It doesn't make sense for the file to have an internal position marker and for reads/writes to occur from there, because the operations can be initiated before previous ones are completed and the order they occur in is not guaranteed. For the same reason, there are no methods in the AsynchronousFileChannel
API that set or query the position, as there are in FileChannel
.
In addition to the read and write methods, an asynchronous lock method is also supported, so that a file can be locked for exclusive access without having to block in the current thread (or poll using tryLock
) if another thread currently holds the lock.
Asynchronous channel groups
Each asynchronous channel constructed belongs to a channel group that shares a pool of Java threads, which are used for handling the completion of initiated asynchronous I/O operations. This might sound like a bit of a cheat, because you could implement most of the asynchronous functionality yourself in Java threads to get the same behaviour, and you'd hope that NIO.2 could be implemented purely using the operating system's asynchronous I/O capabilities for better performance. However, in some cases, it's necessary to use Java threads: for instance, the completion-handler methods are guaranteed to be executed on threads from the pool.
By default, channels constructed with the open()
methods belong to a global channel group that can be configured using the following system variables:
-
java.nio.channels.DefaultThreadPoolthreadFactory
, which defines ajava.util.concurrent.ThreadFactory
to use instead of the default one -
java.nio.channels.DefaultThreadPool.initialSize
, which specifies the thread pool's initial size
Three utility methods in java.nio.channels.AsynchronousChannelGroup
provide a way to create new channel groups:
withCachedThreadPool()
withFixedThreadPool()
withThreadPool()
These methods take either the definition of the thread pool, given as a java.util.concurrent.ExecutorService
, or ajava.util.concurrent.ThreadFactory
. For example, the following call creates a new channel group that has a fixed pool of 10 threads, each of which is constructed with the default thread factory from the Executors
class:
AsynchronousChannelGroup tenThreadGroup =
AsynchronousChannelGroup.withFixedThreadPool(10, Executors.defaultThreadFactory());
The three asynchronous network channels have an alternative version of the open()
method that takes a given channel group to use instead of the default one. For example, this call tells channel
to use the tenThreadGroup
instead of the default channel group to obtain threads when required by the asynchronous operations:
AsynchronousServerSocketChannel channel =
AsynchronousServerSocketChannel.open(tenThreadGroup);
Defining your own channel group allows finer control over the threads used to service the operations and also provides mechanisms for shutting down the threads and awaiting termination. Listing 3 shows an example:
Listing 3. Controlling thread shutdown with a channel group
// first initiate a call that won't be satisfied
channel.accept(null, completionHandler);
// once the operation has been set off, the channel group can
// be used to control the shutdown
if (!tenThreadGroup.isShutdown()) {
// once the group is shut down no more channels can be created with it
tenThreadGroup.shutdown();
}
if (!tenThreadGroup.isTerminated()) {
// forcibly shutdown, the channel will be closed and the accept will abort
tenThreadGroup.shutdownNow();
}
// the group should be able to terminate now, wait for a maximum of 10 seconds
tenThreadGroup.awaitTermination(10, TimeUnit.SECONDS);
The AsynchronousFileChannel
differs from the other channels in that, in order to use a custom thread pool, the open()
method takes anExecutorService
instead of an AsynchronousChannelGroup
.
Asynchronous datagram channels and multicasting
The final new channel is the AsynchronousDatagramChannel
. It's similar to the AsynchronousSocketChannel
but worth mentioning separately because the NIO.2 API adds support for multicasting to the channel level, whereas in NIO it is only supported at the level of theMulticastDatagramSocket
. The functionality is also available in java.nio.channels.DatagramChannel
from Java 7.
An AsynchronousDatagramChannel
to use as a server can be constructed as follows:
AsynchronousDatagramChannel server = AsynchronousDatagramChannel.open().bind(null);
Next, we set up a client to receive datagrams broadcast to a multicast address. First, we must choose an address in the multicast range (from 224.0.0.0 to and including 239.255.255.255), and also a port that all clients can bind to:
// specify an arbitrary port and address in the range
int port = 5239;
InetAddress group = InetAddress.getByName("226.18.84.25");
We also require a reference to which network interface to use:
// find a NetworkInterface that supports multicasting
NetworkInterface networkInterface = NetworkInterface.getByName("eth0");
Now, we open the datagram channel and set up the options for multicasting, as shown in Listing 4:
Listing 4. Opening a datagram channel and setting multicast options
// the channel should be opened with the appropriate protocol family,
// use the defined channel group or pass in null to use the default channel group
AsynchronousDatagramChannel client =
AsynchronousDatagramChannel.open(StandardProtocolFamily.INET, tenThreadGroup);
// enable binding multiple sockets to the same address
client.setOption(StandardSocketOption.SO_REUSEADDR, true);
// bind to the port
client.bind(new InetSocketAddress(port));
// set the interface for sending datagrams
client.setOption(StandardSocketOption.IP_MULTICAST_IF, networkInterface);
The client can join the multicast group in the following way:
MembershipKey key = client.join(group, networkInterface);
The java.util.channels.MembershipKey
is a new class that provides control over the group membership. Using the key you can drop the membership, block and unblock datagrams from certain addresses, and return information about the group and channel.
The server can then send a datagram to the address and port for the client to receive, as shown in Listing 5:
Listing 5. Sending and receiving a datagram
// send message
ByteBuffer message = ByteBuffer.wrap("Hello to all listeners".getBytes());
server.send(message, new InetSocketAddress(group, port)); // receive message
final ByteBuffer buffer = ByteBuffer.allocate(100);
client.receive(buffer, null, new CompletionHandler<SocketAddress, Object>() {
@Override
public void completed(SocketAddress address, Object attachment) {
System.out.println("Message from " + address + ": " +
new String(buffer.array()));
} @Override
public void failed(Throwable e, Object attachment) {
System.err.println("Error receiving datagram");
e.printStackTrace();
}
});
Multiple clients can also be created on the same port and joined to the multicast group to receive the datagrams sent from the server.
Conclusion
NIO.2's asynchronous channel APIs provide a convenient and standard way of performing asynchronous operations platform-independently. They allow application developers to write programs that use asynchronous I/O in a clear manner, without having to define their own Java threads and, in addition, may give performance improvements by using the asynchronous support on the underlying OS. As with many Java APIs, the amount that the API can exploit an OS's native asynchronous capabilities will depend on the support for that platform.
Part 2: The file system APIs
This article completes our two-part introduction to More New I/O APIs for Java (NIO.2) in Java 7. Like the asynchronous channel APIs explored in Part 1, NIO.2's file system APIs fill some significant gaps in the way previous Java versions handle I/O. According to the NIO.2 Java specification request (JSR 203):
The Java platform has long needed a filesystem interface better than the
java.io.File
class. That class does not handle filenames in a way that works consistently across platforms, it does not support efficient file-attribute access, it does not allow sophisticated applications to take advantage of filesystem-specific features (for example, symbolic links) when available, and many of its methods simply return false on error instead of throwing an informative exception.
Coming to the rescue are three new file system packages in the Java 7 beta:
java.nio.file
java.nio.file.attribute
java.nio.file.spi
This article focuses on the most useful classes in these packages:
-
java.nio.file.Files
andjava.nio.file.FileVisitor
allow you to walk through file systems, querying files or directories up to a certain depth and executing user-implemented callback methods for each one found. -
java.nio.file.Path
andjava.nio.file.WatchService
allow you to register to "watch" a specific directory. An application watching directories receives notification if files in those directories are created, modified, or deleted. -
java.nio.attribute.*AttributeView
allow you to view file and directory attributes that were previously hidden from Java users. These attributes include file owner and group permissions, access-control lists (ACLs), and extended file attributes.
We'll provide examples that show how to use these classes. The examples are available in a runnable state (see Download), and you can try them out on the Java 7 betas available from IBM® and Oracle (both still under development at the time of this writing; see Resources).
File visitors
Our first example demonstrates the new FileVisitor
API.
Imagine a scenario in which you want to traverse a directory tree recursively, stopping at each file and directory under that tree and having your own callback methods invoked for each entry found. In previous Java versions, this would have been a painful process involving recursively listing directories, inspecting their entries, and invoking the callbacks yourself. In Java 7, this is all provided via the FileVisitor
API, and using it couldn't be simpler.
The first step is to implement your own FileVisitor
class. This class contains the callback methods that the file-visitor engine will invoke as it traverses the file system. The FileVisitor
interface consists of five methods, listed here in the typical order they would be called during traversal (T
here stands for either java.nio.file.Path
or a superclass):
-
FileVisitResult preVisitDirectory(T dir)
is called before the entries in that directory are visited. It returns one of theFileVisitResult
's enum values to tell the file visitor API what to do next. -
FileVisitResult preVisitDirectoryFailed(T dir, IOException exception)
is called when a directory could not be visited for some reason. The exception that caused the visit to fail is specified in the second parameter. -
FileVisitResult visitFile(T file, BasicFileAttributes attribs)
is called when a file in the current directory is being visited. The attributes for this file are passed into the second parameter. (You'll learn more about file attributes in this article's File attributes section.) -
FileVisitResult visitFileFailed(T file, IOException exception)
is called when the visit to a file has failed. The second parameter specifies the exception that caused the visit to fail. -
FileVisitResult postVisitDirectory(T dir, IOException exception)
is called after the visit to a directory and all its subdirectories has completed. The exception parameter is null when the directory visit has been successful, or it contains the exception that caused the directory visit to end prematurely.
To help developers save time, NIO.2 has kindly provided an implementation of the FileVisitor
interface: java.nio.file.SimpleFileVisitor
. This class is as basic as it gets: for the *Failed()
methods, it just rethrows the exception, and for the other methods it continues without doing anything at all! What's useful about this class is that you can use anonymous classes to override only the methods you want; the rest of the methods are implemented by default.
Listing 1 shows how we create an example FileVisitor
instance:
Listing 1. FileVisitor
implementation
FileVisitor<Path> myFileVisitor = new SimpleFileVisitor<Path>() { @Override
public FileVisitResult preVisitDirectory(Path dir) {
System.out.println("I'm about to visit the "+dir+" directory");
return FileVisitResult.CONTINUE;
} @Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attribs) { System.out.println("I'm visiting file "+file+" which has size " +attribs.size());
return FileVisitResult.CONTINUE;
} };
The FileVisitor
implementation in Listing 1 should print a message for each directory and file it visits and also give the size of the files from their BasicFileAttributes
.
Next we want to create a Path
from which to start our file visiting. This is done using the java.nio.Paths
class:
Path headDir = Paths.get("headDir");
We can use either of two methods on the java.nio.Files
class to start the tree traversal:
-
public static void walkFileTree(Path head, FileVisitor<? super Path> fileVisitor)
walks the file tree under the head directory, invoking the callback methods implemented infileVisitor
as it goes. -
public static void walkFileTree(Path head, Set<FileVisitOption> options, int depth, FileVisitor<? super Path> fileVisitor)
is similar to the preceding method but gives you two additional parameters to specify visit options and how many directories deep into the file tree the traversal should go.
We'll use the simpler version of the walkFileTree()
method to start the process of walking the file tree:
Files.walkFileTree(headDir, myFileVisitor);
Suppose the directory structure looks like this:
headDir
|--- myFile1
|--- mySubDirectory1
| \myFile2
\--- mySubDirectory2
|--- myFile3
\--- mySubdirectory3
\---myFile4
Listing 2 shows the output from this example:
Listing 2. FileVisitor
output
I'm about to visit the headDir directory
I'm about to visit the headDir\mySubDirectory2 directory
I'm about to visit the headDir\mySubDirectory2\mySubDirectory3 directory
I'm visiting file headDir\mySubDirectory2\mySubDirectory3\myFile4 which has size 2
I'm visiting file headDir\mySubDirectory2\myFile3 which has size 2
I'm about to visit the headDir\mySubDirectory1 directory
I'm visiting file headDir\mySubDirectory1\myFile2 which has size 2
I'm visiting file headDir\myFile1 which has size 2
As you can see, the file traversal is depth first but not necessarily in any alphabetical order within a directory. Our callback methods were invoked as expected, and we can see that all the files in the tree have been listed and all the directories have been visited.
In only about 15 lines, we've created a file visitor that will walk any file tree you give it and inspect the files contained therein. This example is basic, but the callbacks can be implemented to be as complex as you want.
Directory watching
This second example covers the exciting world of the new WatchService
API and its associated classes.
The scenario for this example is simple: You'd like to track whether any files or directories in a particular directory (or directories) are being created, modified, or deleted. You might use this information to update a file listing in a GUI display or perhaps to detect the modification of configuration files that could then be reloaded. In previous Java versions, you must implement an agent running in a separate thread that keeps track of all the contents of the directories you wish to watch, constantly polling the file system to see if anything relevant has happened. In Java 7, the WatchService
API provides the ability to watch directories. It removes all the complexity of writing your own file system poller and, where possible, is based upon existing native system APIs for better performance.
The first step is to create a WatchService
instance via the java.nio.file.FileSystems
class. We won't go into the details of file systems in this article, so just know that in most cases you'll want to get the default file system and then invoke its newWatchService()
method:
WatchService watchService = FileSystems.getDefault().newWatchService();
Now that we have our watch service instance, we want to register a path to watch. We create a Path
object for the directory we wish to watch in a slightly different way from the file visitor example, so that we can use its File
instance again later:
File watchDirFile = new File("watchDir");
Path watchDirPath = watchDirFile.toPath();
The Path
class implements the java.nio.file.Watchable
interface, and that interface defines the register()
method we'll be using in this example. WatchKey register(WatchService watchService, WatchEvent.Kind<?>... events)
registers the Path
this method is called on with the specified watchService
for the specific events given. Events trigger a notification only if they are specified in the register call.
For the default WatchService
implementation, the java.nio.file.StandardWatchEventKind
class defines three static implementations ofwatchEvent.Kind
that can be used in the register()
calls:
-
StandardWatchEventKind.ENTRY_CREATE
indicates that a file or directory has been created within the registeredPath
. AnENTRY_CREATE
event is also triggered when a file is renamed or moved into this directory. -
StandardWatchEventKind.ENTRY_MODIFY
indicates that a file or directory in the registeredPath
has been modified. Exactly which events constitute a modification is somewhat platform-specific, but suffice it to say that actually modifying the contents of a file always triggers a modify event. On some platforms, changing attributes of files can also trigger this event. -
StandardWatchEventKind.ENTRY_DELETE
indicates that a file or directory has been deleted from the registeredPath
. AnENTRY_DELETE
event is also triggered when a file is renamed or moved out of this directory.
For our example, let's watch the ENTRY_CREATE
and ENTRY_MODIFY
events, but not ENTRY_DELETE
:
WatchKey watchKey = watchDirPath.register(watchService,
StandardWatchEventKind.ENTRY_CREATE, StandardWatchEventKind.ENTRY_MODIFY);
Our Path
is now registered to be watched, and the WatchService
will work away silently in the background, watching that directory intently. The same WatchService
instance can watch multiple directories by using the same Path
creation and register()
calls we've shown above.
The observant among you may have spotted that the register()
method call returns a class we haven't come across before: WatchKey
. This class represents your registration with the WatchService
. Whether you hang onto this reference or not is up to you, because the WatchService
returns the relevant WatchKey
to you when an event is triggered. However, do note that there are no method calls to find out which directory theWatchKey
was registered with, so if you're watching multiple directories you may wish to track which WatchKey
s go with which Path
s. When you're done with a particular WatchKey
and the events it's registered for, you can cancel its registration with the WatchService
simply by calling its cancel()
method.
Now that our Path
is registered, we can check in with the WatchService
at our convenience to see if any of the events we were interested in has occurred. WatchService
provides three methods for checking if anything exciting has happened:
-
WatchKey poll()
returns the nextWatchKey
that has had some of its events occur, ornull
if no registered events have happened. -
WatchKey poll(long timeout, TimeUnit unit)
takes a timeout and time units (java.util.concurrent.TimeUnit
). If an event occurs during the specified time period, this method exits, returning the relevantWatchKey
. If there are noWatchKeys
to return by the end of the timeout, this method returnsnull
. -
WatchKey take()
is similar to the preceding methods, except it will wait indefinitely until aWatchKey
is available to return.
Once a WatchKey
has been returned by one of these three methods, it will not be returned by a further poll()
or take()
call until its reset()
method is invoked, even if events it is registered for occur. Once a WatchKey
is returned by the WatchService
, you can inspect its events that have been triggered by calling the WatchKey
's pollEvents()
method, which returns a List of WatchEvent
s.
To illustrate, the simple example in Listing 3 continues from the WatchKey
we registered earlier:
Listing 3. Using pollEvents()
// Create a file inside our watched directory
File tempFile = new File(watchDirFile, "tempFile");
tempFile.createNewFile(); // Now call take() and see if the event has been registered
WatchKey watchKey = watchService.take();
for (WatchEvent<?> event : watchKey.pollEvents()) {
System.out.println(
"An event was found after file creation of kind " + event.kind()
+ ". The event occurred on file " + event.context() + ".");
}
When executed, the code in Listing 3 prints:
An event was found after file creation of kind ENTRY_CREATE. The event occurred
on file tempFile.
An event was found after file creation of kind ENTRY_MODIFY. The event occurred
on file tempFile.
As you can see, we got the ENTRY_CREATE
event for the newly created tempFile
as expected, but we also got another event. On some operating systems, a file creation or deletion can sometimes also produce an ENTRY_MODIFY
event. If we had not registered to receive modification events, then we would have only gotten the ENTRY_CREATE
event, whatever the OS.
Extended sample code (demonstrating file modification and deletion for the registered WatchKey
in this section's example) is included in the sample code download.
File attributes
Our third and final example covers the new APIs for getting and setting file attributes using the classes under the java.nio.file.attribute
package.
The new APIs offer access to more file attributes than you can imagine. In previous Java releases, you can get only a basic set of file attributes (size, modification time, whether the file is hidden, and whether it is a file or directory). To get or modify any further file attributes, you must implement this yourself in native code specific to the platforms you want to run on — not exactly easy. Brilliantly, Java 7 should allow you to read and, where possible, modify an extended set of attributes in a simple way via the java.nio.file.attribute
classes, completely abstracting away the platform-specific nature of these operations.
Seven attribute views are available in the new APIs, some of which are specific to the operating system. These "view" classes allow you to get and set whichever attributes they are associated with, and each one has a counterpart attribute class that contains the actual attribute information. Let's take a look at them in turn.
AclFileAttributeView
and AclEntry
AclFileAttributeView
allows you to get and set the ACL and file-owner attributes of a particular file. Its getAcl()
method returns a List
ofAclEntry
objects, one for each permission set on the file. Its setAcl(List<AclEntry>)
method allows you to modify that access list. This attribute view is only available for Microsoft® Windows® systems.
BasicFileAttributeView
and BasicFileAttributes
This view class allows you to get a set of — no surprise — basic file attributes, building on those available in previous Java versions. ItsreadAttributes()
method returns a BasicFileAttributes
instance containing details of last modified time, last access time, creation time, size, and type of file (regular file, directory, symbolic link, or other). This attribute view is available on all platforms.
Let's look at an example of this view. To get a file attribute view for a particular file we start, as always, by creating a Path
object for the file we're interested in:
File attribFile = new File("attribFile");
Path attribPath = attribFile.toPath();
To get the file attribute view we want, we'll use the getFileAttributeView(Class viewClass)
method on Path
. To get theBasicFileAttributeView
for attribPath
, we simply call:
BasicFileAttributeView basicView
= attribPath.getFileAttributeView(BasicFileAttributeView.class);
As described earlier, to get the BasicFileAttributes
from BasicFileAttributeView
, we just call its readAttributes()
method:
BasicFileAttributes basicAttribs = basicView.readAttributes();
And that's it. Now you have all of the basic file attributes for that file to do whatever you wish with. For the BasicFileAttributes
, only the creation, last-modified, and last-access times can be altered (because it wouldn't make sense to change the size or type of file). To change these, we can use the java.nio.file.attribute.FileTime
class to create a new time and then call the setTimes()
method onBasicFileAttributeView
. For example, we could move the last-modified time for our file a minute further into the future:
FileTime newModTime
= FileTime.fromMillis(basicAttribs.lastModifiedTime().toMillis() + 60000);
basicView.setTimes(newModTime, null, null);
The two null
s indicate that we do not want to change the last access time or creation time for this file. If you check the basic attributes again, in the same way we did earlier, you should see that the last modified time has been altered but the creation time and last access time have remained the same.
DosFileAttributeView
and DosFileAttributes
This view class allows you to get attributes specific to DOS. (As you might guess, this view is for Windows systems only.) Its readAttributes()
method returns a DosFileAttributes
instance containing details of whether the file in question is read-only, hidden, a system file, and an archive. The view also has set*(boolean)
methods for each of these properties.
FileOwnerAttributeView
and UserPrincipal
This view class allows you to get and set the owner of a particular file. Its getOwner()
method returns a UserPrincipal
(also in thejava.nio.file.attribute
package), which in turn has a getName()
method returning a String
containing the owner's name. The view also provides a setOwner(UserPrincipal)
method allowing you to change a file's owner. This view is available on all platforms.
FileStoreSpaceAttributeView
and FileStoreSpaceAttributes
This catchily named class allows you to get information about a particular file store. Its readAttributes()
method returns aFileStoreSpaceAttributes
instance containing details of the total space, the unallocated space, and the usable space on the file store. This view is available on all platforms.
PosixFileAttributeView
and PosixFileAttributes
This view class, available on UNIX® systems only, allows you to get and set attributes specific to POSIX (Portable Operating System Interface). Its readAttributes()
method returns a PosixFileAttributes
instance containing details of the owner, group owner, and file permissions for this file (those you would normally set using the UNIX chmod
command). The view also provides setOwner(UserPrincipal)
,setGroup(GroupPrincipal)
, and setPermissions(Set<PosixFilePermission>)
methods to modify these attributes.
UserDefinedFileAttributeView
and String
This view class, available only on Windows, allows you to get and set extended attributes on files. These attributes are unlike the others in that they are just name-value pairs and can be set to anything you wish. This can be useful if you want to add some hidden metadata to a file without altering the file's content. The view provides a list()
method that returns a List
of String
names of the extended attributes for the relevant file.
To get the contents of a particular attribute once you have its name, the view has a size(String name)
method to return the size of the attribute's value and a read(String name, ByteBuffer dest)
method to read the attribute value into the ByteBuffer
. The view also provides awrite(String name, ByteBuffer source)
method to create or alter an attribute, and also a delete(String name)
method to remove an existing attribute entirely.
This is probably the most interesting new attribute view because it allows you to add attributes with arbitrary String
names and ByteBuffer
values to files. That's right — its value is a ByteBuffer
, so you can store any binary data in there you want.
First, we'll get the attribute view:
UserDefinedFileAttributeView userView
= attribPath.getFileAttributeView(UserDefinedFileAttributeView.class);
To get a list of the user-defined attribute names for this file, we call the list()
method on the view:
List<String> attribList = userView.list();
Once we have a particular attribute name we wish to get the associated value for, we allocate a ByteBuffer
of the right size for the value and then call the view's read(String, ByteBuffer)
method:
ByteBuffer attribValue = ByteBuffer.allocate(userView.size(attribName));
userView.read(attribName, attribValue);
attribValue
now contains whatever data was stored for that particular attribute. To set your own attribute, you simply need to create yourByteBuffer
and fill it with whatever data you wish and then call the write(String, ByteBuffer)
method on the view:
userView.write(attribName, attribValue);
Writing an attribute either creates that attribute or overwrites an existing attribute with the same name.
And with that, we conclude the third and final example. Full example code demonstrating four of the attribute views (BasicFileAttributeView
,FileOwnerAttributeView
, FileStoreSpaceAttributeView
, and UserDefinedAttributeView
) is included in the sample code download.
Conclusion
In addition to the topics covered in this article are a number of other NIO.2 file APIs. With Java 7 comes the new ability to create, inspect, and modify symbolic links. There are also new classes allowing access to lower-level information about the file system, and even to supply providers (called FileSystem
and FileStore
) to access any type of file system you wish.
In summary, NIO.2 gives Java developers a simple, consistent, and powerful set of APIs to interact with the file system. Its aim is to abstract away some of the complex platform-specific details of dealing with files and directories and to give programmers more power and flexibility, and it has achieved this well.
http://www.ibm.com/developerworks/java/library/j-nio2-1/index.html
http://www.ibm.com/developerworks/java/library/j-nio2-2/index.html?ca=dat