Java NIO.2 is a channel-based, buffer-oriented technique to handle input/output operations. This is a complementary extension of the conventional stream based input/output system in Java. However, the approach is different and perhaps is the reason to put it in a separate package called java.nio rather than clubbing it into java.io. The article explores some of the key aspects of new input/output APIs introduced in NIO with code examples.
Standard Input/Output System in Java
An input/output operation at the low level is a complicated procedure and can bog down performance unless handled carefully. The java.io package provides the standard APIs for handling system input/output through stream objects. By stream, we mean a sequence of data that may be viewed as a sequence of bits. Standard Java input/output supports serialization and read/write of various data types, such as bytes, primitive types, or an object that reciprocates varied endpoint devices. It is these streams that are converted into required objects and Java types. The designated abstract classes, InputStream and OutputStream, encompass the means to consume and produce streams of data through many of its methods and subclasses. The APIs provided are relatively high level which, though flexible, can provide performance bottlenecks on occasion. NIO.2 gave a new dimension to input/output handling with an emphasis on performance that complements the existing system.
package org.mano.example; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; public class StandardFileHandling { public static void main(String[] args) { BufferedReader reader = null; try { reader = new BufferedReader(new FileReader("testfile.txt")); String str; do { str = reader.readLine(); System.out.println(str); } while (str != null); } catch (IOException ex1) { System.out.println("IO Error " + ex1.getMessage()); } finally { try { if (reader != null) reader.close(); } catch (IOException ex2) { System.out.println("IO Error " + ex2.getMessage()); } } } }
New Input/Output System in Java
NIO.2 was created, keeping in view performance without resorting to native codes. This, however, does not mean that stream-based input/output is inefficient. But, there are occasions where NIO.2 can perform better. For example, following are some hints for choosing NIO.2 vi-a-vis conventional input/output.
- NIO.2 delegates some of the responsibilities of time-consuming activity such as input/output buffering to the underlying OS. This improves performance.
- NIO.2 uses a block-oriented approach of data streaming rather than java.io‘s sequential streaming.
- Sequential streaming is excellent for chained filtering but bad for cached data manipulation.
- NIO.2 deals with data in blocks; the blocks are produced and consumed through buffers. As a result, moving to and fro within the buffered data gives more flexibility during processing.
- Because NIO.2 streaming is channel based, a single thread can manage multiple threads.
- Blocking a stream of NIO.2 is not good for parsing. Conventional input/output is comparatively simple and efficient due to its sequential bits transmission.
NIO.2 Buffer and Channel Overview
As we know, NIO.2 is buffer oriented and channel based; these two features are its basic building blocks. The buffer is the memory that holds the data and the channel is the media through which an open connection transmits data such as a file or a socket.
All the buffer classes, such as ByteBuffer, CharBuffer, and so forth, are the subclasses of the abstract Buffer class defined in the java.nio package. A buffer can be deemed as an array of Java elements limited by the number of its contents contained. The current index referred by the buffer indicates the next read/write element. The current index is advanced on each read/write operation.
Channels define the open connection endpoints for input/output operations. It is defined in the java.nio.channels package. It can be viewed as a connection pipe with open ends at the source and destination. The Channels class implements the Channel interface that extends the AutoCloseable and Closeable interfaces. Due to its AutoCloseable extension, we can use try with a resource block and the channel is closed automatically when no longer needed.
package org.mano.example; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.SeekableByteChannel; import java.nio.file.Files; import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.nio.file.Paths; public class NIOFileHandling { public static void main(String[] args) { Path filePath = null; try { filePath = Paths.get("testfile.txt"); } catch (InvalidPathException ex1) { System.out.println("Path Error " + ex1); return; } try (SeekableByteChannel channel = Files.newByteChannel(filePath)) { ByteBuffer buff = ByteBuffer.allocate(10); while (channel.read(buff) > 0) { buff.flip(); for (int i = 0; i < buff.limit(); i++) { System.out.print((char) buff.get()); } buff.clear(); } System.out.println(); } catch (IOException ex) { System.out.println("I/O error " + ex); } } }
Charsets and Selectors
Charsets work with the raw data bytes poured in and out through channels. By defining the way of mapping bytes to characters, it actually encodes and decodes data according to a read/write operation. Encoding refers to converting a sequence of characters into bytes and decoding refers to converting a sequence of bytes into characters. We do not often use Charsets because default encoders and decoders are provided. However, if need be, we easily can override the default encoders and decoders with our own. Charsets, encoders, and decoders are supported by the classes provided in the java.nio.charset package.
Selectors are a bit tricky, with a lot of concepts involved, such as Synchronous/Asynchronous I/O. This requires an exclusive article and would simply go out of scope in exploring the concept. At the very fundamental level, it provides the service that enables us to ask a channel if it is ready to perform an input/output operation. For example, we can inquire a channel object if it has any byte ready for reading or has any incoming connections ready to accept, and so on. Selectors are supported by classes defined in the java.nio.channels package and most are applicable to socket backed channels.
The Path Interface
NIO.2 improvements with JDK7 include three new packages: java.nio.file, java.nio.file.attribute, and java.nio.file.attribute. Apart from these, perhaps the most important is the Path interface. Path now encompasses a path to a file that describes a file location with a directory structure. It also provides features to bind many NIO.2 file attributes, such as the getName(int index) method. This method returns a path object containing a path name element referred by the positional parameter as an index. A value of zero indicates that the element is closest to the root in the supplied absolute path. The method getNameCount() returns the number of name elements in the path. Observe the following code snippets.
A Path object created:
Path filePath = Paths.get("/home/mano/testfile.txt");
This will print home as the root element:
System.out.println("Root is :" +filePath.getName(0).toString());
This will print mano as the next name element after root referred by index 1:
System.out.println("name element next to root :" +filePath.getName(1).toString());
This will print the number of the name element in the path; for example 3:
System.out.println("Total name elements in the path :" +filePath.getNameCount());
Conclusion
NIO.2 simply added new features to input/output handling in Java. It also paved the way for managing multiple channels using a few threads. This is particularly useful in server applications that require a multiple channeled open connection. Also, in applications that require handling bulk data through a single channel, using NIO.2 may also be advantageous.