Exploring JDK 7, Part 4: New I/O: The Next Generation
- New Filesystem Interface
- Asynchronous I/O
- Completion of Socket-Channel Functionality
- Conclusion
Oracle’s release of JDK 7 is expected to occur this coming fall. This new release will offer a suite of new features for you to learn.
This final article in a four-part series that introduces you to some of these features presents the next generation of New I/O. If this is your first stop, be sure to read Part 1, Part 2, and Part 3 first.
Back in 2000, Sun Microsystems launched JSR 51: New I/O APIs for the Java Platform to develop APIs that provide access to an operating system's low-level input/output operations for improving performance. Introduced as part of J2SE 1.4, and according to Wikipedia's New I/O entry, New I/O (NIO) consists of the following APIs:
- Buffers for data of primitive types
- Character set encoders and decoders
- A pattern-matching facility based on Perl-style regular expressions
- Channels, a new primitive I/O abstraction
- A file interface that supports locks and memory mapping of files up to Integer.MAX_VALUE bytes (2GB)
- A multiplexed, non-blocking I/O facility for writing scalable servers (based upon selectors and keys)
JSR 203: More New I/O APIs for the Java Platform ("NIO.2") picks up from where JSR 51 left off. NIO.2, as it's commonly known, addresses significant problems with the java.awt.File-based filesystem interface, introduces asynchronous I/O, and completes functionality not included with JSR 51. The following major components are included in JSR 203:
- A new filesystem interface that supports bulk access to file attributes, change notification, escape to filesystem-specific APIs, and a service-provider interface for pluggable filesystem implementations
- An API for asynchronous (as opposed to polled, non-blocking) I/O operations on both sockets and files
- The completion of the socket-channel functionality defined in JSR 51, including the addition of support for binding, option configuration, and multicast datagrams
This article introduces you to NIO.2.
New Filesystem Interface
Java's File class suffers from significant problems. For example, the delete() and mkdir() methods return a status code instead of throwing an exception when something goes wrong; there's no way to tell the failure's cause. Additional problems include the following:
- File doesn't provide a method to detect symbolic links. To learn why symbolic link detection is important, and to discover attempts to fix this problem, check out Patrick’s article, How to deal with filesystem softlinks/symbolic links in Java and Links/Aliases/Shortcuts in Java.
- File provides access to a limited set of file attributes. It doesn't provide access to file permissions and access control lists.
- File doesn't provide a way to access all of a file's attributes (such as a file's modification time and its type) at one time, which exacts a performance penalty because the filesystem is queried for each attribute request.
- File's list() and listFiles() methods, which return arrays of filenames and directory names, don't scale to large directories. When listing a large directory over a network, a list()/listFiles() method call might block the current thread for a long period of time. In severe cases, the virtual machine might run out of memory.
- File doesn't provide methods to copy and move files. Although File provides a renameTo() method that, in some cases, can be used to move a file, its behavior is platform-dependent, meaning that it behaves inconsistently across platforms. According to renameTo()'s documentation, this method might not be able to move a file from one filesystem to another, it might not be atomic, and it might not succeed if a file with the destination abstract pathname already exists.
- File doesn't provide a change notification facility, which requires an application to poll for changes, resulting in a performance penalty. For example, a server that needs to determine when a new JAR file has been added to a directory would need to poll that directory so that it could load (or reload) the JAR file. Performance suffers because the server's background thread needs to hit the filesystem for each poll.
- File doesn't allow developers to introduce their own filesystem implementations. For example, a developer might want to store a filesystem in a zip file, or even create an in-memory filesystem.
NIO.2 introduces a new filesystem interface that overcomes these problems and more. This interface consists of the classes and other types located in the new java.nio.file, java.nio.file.attribute, and java.nio.file.spi packages.
These packages provide several entry points. One of these entry points is the java.nio.file.Paths class, which provides a pair of methods for returning a java.nio.file.Path instance (a file reference that locates a file, which doesn't have to exist, via a system-dependent path):
- public static Path get(String path) constructs a Path instance by converting the given path string and returns this instance.
- public static Path get(URI uri) constructs a Path instance by converting the given path Uniform Resource Identifier and returns this instance.
Once you have a Path instance, you can use this instance to perform a variety of path operations (returning parts of the path and joining two paths, for example) and a variety of file operations (such as deleting, moving, and copying files).
The need for brevity prevents me from thoroughly exploring Path. For that reason, I'm only demonstrating the former get() method and Path's delete() method in the context of Listing 1's small utility application.
Listing 1InformedDelete.java
// InformedDelete.java import java.io.IOException; import java.nio.file.DirectoryNotEmptyException; import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.Paths; public class InformedDelete { public static void main (String [] args) { if (args.length != 1) { System.err.println ("usage: java InformedDelete path"); return; } // Attempt to construct a Path instance by converting the path argument // string. If unsuccessful (you passed an empty string as the // command-line argument), the get() method throws an instance of the // unchecked java.nio.file.InvalidPathException class. Path path = Paths.get (args [0]); try { path.delete (); // Attempt to delete the path. } catch (NoSuchFileException e) { System.err.format ("%s: no such file or directory%n", path); } catch (DirectoryNotEmptyException e) { System.err.format ("%s: directory not empty%n", path); } catch (IOException e) { System.err.format ("%s: %s%n", path, e); } } }
InformedDelete employs Path's delete() method to overcome the problem of File's delete() method not identifying a failure's cause. When Path's delete() method detects a failure, it throws a suitable exception:
- java.nio.file.NoSuchFileException is thrown if the file doesn't exist.
- java.nio.file.DirectoryNotEmptyException is thrown if the file is a directory that couldn't be deleted because the directory contains at least one entry.
- A subclass of java.io.IOException is thrown if some other kind of I/O problem occurs. For example, java.nio.file.AccessDeniedException is thrown if the file is read-only.