NIO Package in Java

Introduction

Java has provided a second I/O system called NIO (Non-blocking I/O). Java NIO provides a different way of working with I/O than the standard I/O APIs. It is an alternate I/O API for Java (from Java 1.4).

Java NIO API is a collection of Java programming language APIs that offer features for intensive I/O operations. It was introduced with the J2SE 1.4 release of Java by Sun Microsystems to complement an existing standard I/O. NIO was developed under the Java Community Process as JSR 51. An extension to NIO that offers a new file system API, called NIO.2, was released with Java SE 7 (“Dolphin”).

The Java NIO APIs are provided in the java.nio package and its sub-packages.

Features of NIO in Java

The APIs of NIO were designed to provide access to the low-level I/O operations of modern operating systems. Although the APIs are themselves relatively high-level, the intent is to facilitate an implementation that can directly use the most efficient operations of the underlying platform. 

  • A file interface that supports locks and memory mapping of files up to Integer.MAX_VALUE bytes (2 GB)
  • Buffers for data of primitive types
  • Character set encoders and decoders
  • A file interface that supports locks and memory mapping
  • A multiplexed, non-blocking I/O facility for writing scalable servers 
  • A pattern-matching facility based on Perl-style regular expressions (in package java.util.regex)

Also, NIO API offers selectors that introduce the functionality of listening to multiple channels for IO events in an asynchronous or non-blocking way. In NIO, the most time-consuming I/O activities include filling and draining of buffers to the operating system, which increases in speed.

Componentes of NIO in Java   

  • Channels
  • Buffers
  • Selectors

Java NIO has more classes and components than these, but the Channel, Buffer, and Selector form the core of the API, in my opinion. The rest of the components, like Pipe and FileLock, are merely utility classes to be used in conjunction with the three core components.  

What is the channel in NIO? 

All IO (Input / Output) starts with a channel in NIO in Java. Data can be read into a Buffer from the channel. Java NIO channels are similar to streams. There are a few points which show the similarities between channels and streams

  • Both can read and write to channels, but streams are typically one-way (read or write).
  • Channels can be read and written asynchronously.
  • Channels always read to and write from a Buffer. 

Channels List

Here are some important channel implementations in Java NIO.

  • FileChannel- reads the data from and to the files.
  • DatagramChannel- reads and writes data over the network via UDP.
  • SocketChannel- reads and writes data over the network via TCP.
  • ServerSocketChannel- allows listening for incoming TCP connections, as a web server does. For each incoming connection, a SocketChannel is created. 

The complete program of the Channel in NIO is listed below.

import java.io.FileInputStream;  
import java.io.FileOutputStream;  
import java.io.IOException;  
import java.nio.ByteBuffer;  
import java.nio.channels.ReadableByteChannel;  
import java.nio.channels.WritableByteChannel;  
   
public class Main {  
    @SuppressWarnings("resource")  
    public static void main(String[] args) throws IOException {  
   
        // Path Of The Input Text File  
        FileInputStream input = new FileInputStream ("sampleInput.txt");  
        ReadableByteChannel source = input.getChannel();    
   
        // Path Of The Output Text File  
        FileOutputStream output = new FileOutputStream ("sampleOutput.txt");  
        WritableByteChannel destination = output.getChannel();    
        copyData(source, destination);  
  
        System.out.println("! File Successfully Copied From Source To Destination !");  
    }  Here are some important channel implementations in Java NIO.
   
    private static void copyData(ReadableByteChannel source, WritableByteChannel destination) throws IOException {  
        ByteBuffer buffer = ByteBuffer.allocateDirect(1024);    
        while (source.read(buffer) != -1) {    
            // The Buffer Is Used To Be Drained  
            buffer.flip();    
   
            // Make Sure That The Buffer Was Fully Drained    
            while (buffer.hasRemaining()) {    
                destination.write(buffer);    
            }  
   
            // Now The Buffer Is Empty!  
            buffer.clear();  
        }   
    }  
}  

Note. Here, first, we have to create 2 ".txt" files named "sampleInput.txt" and "sampleOutput.txt" and then a ".java" file named "Main.java" and compile our following program. If the data of the "sampleInput.txt" file is copied into the other file, the "sampleOutput.txt", it will return the output as shown below.

channel-program-output-image 

What is the Buffer in NIO? 

Java NIO Buffers are used when interacting with NIO channels. Data is written from the buffers into the channels. A buffer is essentially a block of memory into which you can write data, which you can then later read again.

This memory block is wrapped in an NIO Buffer object, which provides a set of methods that make it easier to work with the memory block. 

Use of Buffer 

Using a Buffer to read and write data typically follows these 4 steps.

  1. Write data into the Buffer.
  2. Call buffer.flip()
  3. Read data out of the Buffer
  4. Call buffer.clear() or buffer.compact()

When we write the data into the buffer, it keeps track of how much data has been written. When written data is complete, we need to switch the buffer from writing to reading mode. The flip() method is used for this purpose. In reading mode, we only can read the data from the buffer.

Once we have read the data, we clear the buffer to make it ready for writing again. clear() method or compact() method is used to clear the buffer.  

  • The clear() method clears the whole buffer.
  • The compact() method only clears the data which you have already read.

If there is any unread data available in the buffer, it will move to the beginning of the buffer, and data will now be written into the buffer after the unread data.

Some primary parameters that define NIO buffer in Java are listed below-

  1. Capacity: Maximum amount of data that can be stored in the buffer. Its capacity can not be altered. Once the buffer is full it should be clear before writing in it.
  2. Limit: Limit as the mode of the buffer. Write mode of the buffer is a limit is equal to the maximum capacity of data that could be written in the buffer. On the other side, the read mode limit of the buffer is how much data can be read from the buffer.
  3. Position: It is the index of the next element to be read from the buffer and write into the buffer by the get() and put() method.

Methods of Buffer in NIO Java

  • allocate(int capacity): This method used to allocate a new buffer with the capacity is given as the parameter.
  • read(): This method is used to write data from channel to buffer.
  • put(): This method is used to read data from the buffer.
  • flip(): It switches the mode of a buffer from writing to reading mode.
  • write(): This method is used to write data from the buffer to the channel.
  • get(): This method is used to read data from the buffer. 

The complete program of Buffer in NIO is listed below.

import java.nio.ByteBuffer;  
import java.nio.channels.FileChannel;  
import java.nio.file.Path;  
import java.nio.file.Paths;  
import java.nio.file.StandardOpenOption;  
import java.io.IOException;  
   
public class BufferExample {  
    public static void main (String [] args)   
            throws IOException {  
       
        String testFile = "testfile.txt";  
        Path filePath = Paths.get(testFile);  
       
        writeFile(filePath);  
   
        readFile(filePath);  
    }  
       
    private static void writeFile(Path filePath)  
            throws IOException {  
               
        String input = "Data written in textfile successfully";  
        System.out.println("Text written to file [" + filePath.getFileName() + "]: " + input);  
           
        byte [] inputBytes = input.getBytes();  
        ByteBuffer writeBuffer = ByteBuffer.wrap(inputBytes);  
           
        FileChannel writeChannel = FileChannel.open(filePath, StandardOpenOption.CREATE, StandardOpenOption.WRITE);  
           
        int noOfBytesWritten = writeChannel.write(writeBuffer);  
           
        writeChannel.close();  
    }  
       
    private static void readFile(Path filePath)  
            throws IOException {  
           
        FileChannel readChannel = FileChannel.open(filePath);  
           
        ByteBuffer readBuffer = ByteBuffer.allocate(24);  
        int noOfBytesRead = readChannel.read(readBuffer);  
           
        byte [] bytes = readBuffer.array();  
        String output = new String(bytes).trim();  
           
        System.out.println("Text read from file [" + filePath.getFileName() + "]: " + output);  
           
        readChannel.close();  
    }  
} 

buffer-channel-image 

As shown in the above figure, you read data from a channel into a buffer and write data from a buffer into a channel.

What is a Selector in NIO Java?

Java NIO Supports multiple transactions between the channel and buffers. So, for example, with one or more NIO channels, and for checking which channel is ready for reading and writing, the selector is provided by NIO in Java. Through the selector, we can make a thread to know which channel is ready for writing and reading of data that can deal with that channel. 

By calling the static method open() of the selector, we can find the instance of the selector. 

Methods of Selector

  • attach(): It is used to attach an object with the key. Attaching an object to a channel is to recognize the same channel.
  • attachment(): It is used to retain the attached object from the channel.
  • channel(): It is used to get the channel for which the key is created.
  • selector(): It is used to get the selector for which the key is created.
  • isValid(): This method returns whether the key is valid or not.
  • isReadable(): It states whether the key's channel is ready for reading or not.
  • isWritable(): It states whether the key's channel is ready for writing or not.

The complete program of Selector in NIO is listed below.

import java.io.FileInputStream;  
import java.io.IOException;  
import java.net.InetSocketAddress;  
import java.nio.ByteBuffer;  
import java.nio.channels.FileChannel;  
import java.nio.channels.SelectionKey;  
import java.nio.channels.Selector;  
import java.nio.channels.ServerSocketChannel;  
import java.nio.channels.SocketChannel;  
import java.util.Iterator;  
import java.util.Set;  
  
public class SelectorExample {  
   public static void main(String[] args) throws IOException {  
      String demo_text = "This is a demo String";     
      Selector selector = Selector.open();  
      ServerSocketChannel serverSocket = ServerSocketChannel.open();  
      serverSocket.bind(new InetSocketAddress("localhost", 5454));  
      serverSocket.configureBlocking(false);  
      serverSocket.register(selector, SelectionKey.OP_ACCEPT);  
      ByteBuffer buffer = ByteBuffer.allocate(256);  
      while (true) {  
         selector.select();  
         Set<SelectionKey> selectedKeys = selector.selectedKeys();  
         Iterator<SelectionKey> iter = selectedKeys.iterator();  
         while (iter.hasNext()) {  
            SelectionKey key = iter.next();  
            int interestOps = key.interestOps();  
            System.out.println(interestOps);  
            if (key.isAcceptable()) {  
               SocketChannel client = serverSocket.accept();  
               client.configureBlocking(false);  
               client.register(selector, SelectionKey.OP_READ);  
            }  
            if (key.isReadable()) {  
               SocketChannel client = (SocketChannel) key.channel();  
               client.read(buffer);  
               if (new String(buffer.array()).trim().equals(demo_text)) {  
                  client.close();  
                  System.out.println("Not accepting client messages anymore");  
               }  
               buffer.flip();  
               client.write(buffer);  
               buffer.clear();  
            }  
            iter.remove();  
         }  
      }  
   }  
} 

NIO vs. IO in Java
 

NIO IO
NIO deals with the data blocks, which are chunks of bytes. IO uses streamlined data flow, i.e, one more byte at a time, and relies on converting data objects into bytes and vice versa.
NIO is based on non-blocking I/O operations. IO is based on blocking I/O operations.
NIO channels are bidirectional, meaning a channel can be used for both reading and writing data. IO stream objects are unidirectional.
NIO API also supports multi-threading so that data can be read and written asynchronously. IO does not support multithreading. So, read and write operations cannot be performed asynchronously.
NIO, we use buffer-oriented, which allows accessing data back and forth without the need for caching. IO does not allow us to move back and forth in the data. In the case where we need to move forth and back in the data to read from a stream, we need to cache it in a buffer first.
Multithreading in NIO makes it Nonblocking, which means that the thread is requested to read or write only when data is available; otherwise thread can be used for another task in the meantime. This is not possible in the case of conventional Java IO. 


Summary 

In this article, we learned about the NIO package in Java Programming language and how to use the NIO package in Java programs.