Discover the benefits of java.nio's new features
Takeaway: The java.nio package, which was introduced in Java 1.4, allows developers to achieve greater performance in data processing and offers better scalability. Peter V. Mikhalenko explains how to use many of the NIO API's new features.
The java.nio (new input/output) package, which was introduced in Java 1.4, allows for I/O more like what is available in other lower-level languages like C. Many developers think the new capabilities offer just non-blocking I/O operations; however, the new features offer many other new and interesting features, which include:
- You can memory map files.
- You can read and write blocks of data direct from disk, rather than byte by byte. (It deals with the endian problem when you fish the data out of the buffer not during the read.)
- You can do non-blocking asynchronous I/O.
- You can lock files or parts of files.
Regular I/O is based on stacking the methods you need in a rather verbose and highly asymmetric way in InputStream, Reader, and BufferedReader. The NIO design is cleaner, simpler, and more efficient; you could even do all of your I/O with NIO if you choose. In this article, I'll explore most of these features and discuss how they to use them.
Communication channels
The NIO API introduces a new primitive I/O abstraction called channel. A channel represents an open connection to an entity such as a hardware device, a file, a network socket, or a program component that is capable of performing one or more distinct I/O operations, for example reading or writing. As specified in the java.nio.channels.Channel interface, channels are either open or closed, and they are both asynchronously closeable and interruptible.
A nice feature of a channel is that one thread can be blocked on an operation, and another thread can close the channel. When the channel closes, the blocked thread awakens with an exception indicating that the channel is closed.
There are several interfaces that extend the channel interface, each of which specifies a new I/O operation. Here's information about these interfaces:
- The ReadableByteChannel interface specifies a read() method that reads bytes from the channel into a buffer.
- The WritableByteChannel interface specifies a write() method that writes bytes from a buffer to the channel.
- The ScatteringByteChannel and GatheringByteChannel interfaces extend the ReadableByteChannel and WritableByteChannel interfaces, respectively, adding read() and write() methods that take a sequence of buffers rather than a single buffer.
The FileChannel class supports the usual operations of reading bytes from and writing bytes to a channel connected to a file, as well as those of querying and modifying the current file position and truncating the file to a specific size. It defines methods for acquiring locks on the whole file or on a specific region of a file; these methods return instances of the FileLock class. Finally, it defines methods for forcing updates to the file to be written to the storage device that contains it for efficiently transferring bytes between the file and other channels, and mapping a region of the file directly into memory.
Configuring channels
You can configure channels for blocking or non-blocking operations. When blocking calls to read, write, or other operations, do not return until the operation completes. Large writes over a slow socket, for example, can take a long time. In non-blocking mode, a call to write a large buffer over a slow socket would just queue up the data (probably in an operating system buffer, though it could even queue it up in a buffer on the network card) and return immediately. The thread can move on to other tasks, while the operating system's I/O manager finishes the job.
Moving on from file channels takes us to channels for reading from and writing to socket connections. You can also use these channels in a blocking or non-blocking fashion. In the blocking fashion, they just replace the call to connect or accept, depending on whether you are a client or a server. In the non-blocking fashion, there is no equivalent. Listing A is an example of how you can send the string "Hello there!" to 1234 port and get a response.
Buffers in NIO
Buffer classes provide a mechanism to store a set of primitive data elements in an in-memory container. In addition to its content, a buffer has a position, which is the index of the next element to be read or written, and a limit, which is the index of the first element that should not be read or written. The base Buffer class defines these properties, as well as methods for clearing, flipping, and rewinding; for marking the current position; and for resetting the position to the previous mark. There is a buffer class for each non-Boolean primitive type: ByteBuffer, CharBuffer, DoubleBuffer, LongBuffer, etc.
Each of these buffer classes support a set of get- and put- methods for reading and writing data, and there are a couple of get- and put- methods for each primitive type that can potentially be read or written from/to the corresponding buffer type. For ByteBuffer, for example, there are methods getChar(), putChar(), getByte(), putByte(), getShort(), putShort(), and some others.
There is one specialized form of direct ByteBuffer known as a MappedByteBuffer. This class represents a buffer of bytes mapped to a file. To map a file to a MappedByteBuffer, you first must get the channel for a file. Once the MappedByteBuffer has been created, you can access it like any other ByteBuffer. In this particular case though, it is read-only, so any attempt to put something with the putXxxx()method will throw a NonWritableChannelException exception.
The java.nio.charset package contains charsets, decoders, and encoders for translating between bytes and Unicode characters. A decoder is an engine that transforms bytes in a specific charset into characters; an encoder is an engine that transforms characters into bytes. In Listing A, the decoder and encoder are used for recoding requests and responses on the fly.
Non-blocking operations
Non-blocking operations are the most popular and widely used feature of the new I/O API. In order to configure the channel to be non-blocking, you should call the configureBlocking() method. For SocketChannel, this is how you do that:
String host = ...;
InetSocketAddress socketAddress =
new InetSocketAddress(host, 1234);
channel = SocketChannel.open();
channel.configureBlocking(false);
channel.connect(socketAddress);
After calling the connect() method, it will return the execution immediately. Then, you have to point out how to handle such channel connections. If a channel is selectable (i.e., it can be multiplexed via the Selector class and extends the SelectableChannel class), you can register your channel with Selector (via the register() method), and then subscribe to specific events; it will notify you when something specified will happen. The events are specified by fields of the SelectionKey class. Here is how you do this:
Selector selector = Selector.open();
channel.register(selector,
SelectionKey.OP_CONNECT | SelectionKey.OP_READ);
To find out if something interesting has happened, you can put a while loop in its own thread.
When you receive a notification, it can be about any of the selected events you were interested in; you will get the Set of ready objects through the selectedKeys method and iterate. The element in the Set is a SelectionKey instance. As long as you have subscribed to the OP_CONNECT and OP_READ events of a ChannelSocket, you have to check isConnectable() and isReadable() properties (see Listing B.) We are removing the currently processed entry with the remove() method because the set of channels can change while you are processing them.
Conclusion
Although many developers think the new I/O capabilities just offer non-blocking I/O operations, this is not completely accurate. The new I/O offers several new concepts (such as channels) and new ways of working with I/O operations through buffers, charsets, and file mapping. The java.nio package allows developers to achieve greater performance in data processing, as well as better scalability.
For more in-depth details about NIO features, take a look at Sun's JSR 51 and the quick Sun NIO guide.
Peter V. Mikhalenko is a Sun certified professional who works for Deutsche Bank as business consultant.
SponsoredWhite Papers, Webcasts, and Downloads
- Self-Tuning Disk Drives Eliminate Performance Bottlenecks and Heighten ROI Diskeeper
- Voice over IP Reliability: Architecture Matters ShoreTel
- Defrag Myth Busters - What You Should Know Diskeeper
- How File Fragmentation Occurs on Windows XP / Windows Server 2003 Diskeeper
- The Shortcut Guide to Managing Disk Fragmentation - Chapter 1 Diskeeper
Article Categories
- Security
- Security Solutions, IT Locksmith
- Networking and Communications
- E-mail Administration NetNote, Cisco Routers and Switches
- CIO and IT Management
- Project Management, CIO Issues, Strategies that Scale
- Desktops, Laptops & OS
- Windows 2000 Professional, Microsoft Word, Microsoft Excel, Microsoft Access, Windows XP,
- Data Management
- Oracle, SQL Server
- Servers
- Windows NT, Linux NetNote, Windows Server 2003
- Career Development
- Geek Trivia
- Software/Web Development
- Web Development Zone, Visual Basic, .NET
