- Writing a Java NIO Filesystem, Part 1
My recent project, the NSF SFTP File Store consists of two main parts: the SFTP server itself (powered by Apache Mina) and a Java NIO filesystem implementation. I casually mentioned the latter in my introductory post, but I think it's an interesting topic that warrants a post or two of its own.
Introduction to NIO
The term "Java NIO" refers to the non-blocking IO package added in two parts across Java 6 and 7 and can be considered a refresh of previous capabilities in a similar way to the Collections API in Java 2 replaced the handful of original collection classes.
The initial (and larger) part of it added in Java 6 added better capabilities for dealing with all sorts of "byte stuff": buffers of arbitrary types, smoother character-set handling, more-flexible streams, and so forth. I dealt with these constructs a while ago with the "nsfdata" package in ODA. In that case, it proved very useful for dealing with in-memory representations of Notes Composite Data, which is best dealt with as a navigable array of differently-sized structures.
java.nio.file package was added in Java 7 and is a refresh of the older
java.io.FileOutputStream/etc. system. It interacts well with the previous NIO stuff, but can actually be thought of as a distinct thing, despite its shared naming. This package changes around the mechanics of the original filesystem API, and I remember finding it kind of grating when I first encountered it. It's all in service of being more flexible, though, and it's one of those things where repeated exposure ended up making the older API feel weird and wrong.
Using the NIO Filesystem API
Before I get to the actual implementation of a backing filesystem, I think it'll make sense to show some examples of using the NIO filesystem API, especially since these can (and should) be used in any Java-based application today.
When the new classes were added, the older
File class was augmented with a method to convert it to a
java.nio.file.Path and vice-versa:
Path interface is a very-lightweight and implementation-neutral representation of a path. Unlike the
File class, it doesn't have methods for actually interacting with the filesystem. In fact, pretty much all you can do with it specifically is get other
Paths based on it:
The primary way you use
Path objects is via the
Files utility class, which provides not only the methods you may be familiar with from
File but also some additional ones like getting input and output streams:
Here, you can see a couple things going on:
FileOutputStream, and so the object you assign it to should be declared as just
- Many of the
Filesmethods take zero or more open/move/etc. options, and they can be a little odd at first. The method parameters are declared as e.g.
OpenOption, but the actual options are available in the
StandardOpenOptionenum. This is to allow for arbitrary extensions for custom filesystems. For example, you might write a custom option to force creating a backup version of the file before writing.
- I'm using the try-with-resources syntax from Java 7 here. That isn't actually related to NIO except in that it came in the same release, but it's great and you should use it.
Files class also contains methods for listing files and walking file trees, which operate with
Streams (as of Java 8) and callbacks. For example, this bit from the NSF ODP Tooling walks a file tree using the callback method and stores it in a ZIP file:
Next Up: Implementation
In my next post, I'll get into some specifics of implementing a filesystem using this framework as well as some of the implications of how flexible and straightforward it is.