ZOFTINO.COM android and web dev tutorials

Java NIO Tutorial

Java new input output library allows you to perform input and output operations efficiently. Unlike Java I/O classes which read and write data byte by byte using stream objects, Java NIO reads and writes data in blocks using channels which use operating system features.

NIO allows you to create high speed input and output programs as it doesn’t deals with keeping and removing data to and from buffers. NIO uses operating system features to do that. That is why you will get performance improvement in performing input and output operations using NIO API.

Table of Contents

Difference between IO and NIO

The I/O classes found in java.io.* package reads and writes data one byte at a time. Whereas, NIO classes found in java.nio.* package reads and writes data in blocks.

Input and output operations are slow using IO. Using NIO API, input and output operations are performed fast because it relies on operating system features and deals with data in blocks.

The advantage of using IO is that it allows you to use filters and chain of filters on data stream.

With IO, you directly read from and write to stream objects. Using NIO, you read from buffer objects and write to buffer objects. Buffer object in turn uses channel to read data from sources and to write data to destinations. NIO uses system level buffers.

Stream objects are unidirectional meaning you can create a stream for reading or writing. Channels are bidirectional meaning a channel can be used for both reading and writing data.

Using NIO API, data can be read and written asynchronously. You can use NIO API in such as a way that while performing IO operations current thread is not blocked.

NIO API offer selectors which allow you to listen to multiple channels for IO events in asynchronous or non blocking way.

Buffers

Buffers are non-resizable data container objects. You write data to buffers and channels read the data from it and transfer it to destinations. Channels get data from the source, write it to buffers and your program reads data from buffers. NIO API provides buffers which can contain different types of primitive data, such as CharBuffer, DoubleBuffer, FloatBuffer, IntBuffer, ShrotBuffer, etc. ByteBuffer can contain data in bytes. You communicate with Channels using ByteBuffer.

Attributes of Buffers are capacity (maximum number of elements buffer can contain), limit (it tells the end of active buffer content), position (index of the next element to be read and written) and mark (remembers the position when mark() is called, when reset() is called its position will be set to mark.)

To create a buffer, you need to first call allocate() method passing capacity which indicates the number of elements the array inside the buffer can hold.

ByteBuffer bb =  ByteBuffer.allocate(110);

Method allocate() creates non-direct buffer. If you want to create direct buffers, you need to use allocateDirect() method. With direct buffers, IO operations are performed directly on it without using intermediates. But direct buffers are expensive to allocate and de-allocate. Direct buffers improve performance when they are used for handling large amount of data that is operated upon by system native IO operations.

ByteBuffer bb =  ByteBuffer.allocateDirect(1200);

You can also create Buffer object by calling wrap() method and passing appropriate array to it as an argument.

	char offer[] = "upto 20% off on travel".toCharArray();
	CharBuffer cb = CharBuffer.wrap(offer);	

ByteBuffer offers methods such as asIntBuffer(), asFloatBuffer(), asLongBuffer(), asDoubleBuffer(), etc, to convert it into different types of buffer objects.

	IntBuffer ib = bb.asIntBuffer();

To fill buffers, you can use put methods of the buffer object. In the below example, after CharBufer allocated, the capacity and limit are set 50 and position is set to 0. After filling the buffer with the deals string, capacity and limit stays same and position is set to 5. As you keep adding characters to the buffer, position value increases.

	CharBuffer cbuff = CharBuffer.allocate(50);
	cbuff.put("deals");

To access values or to drain buffers, you can use absolute or relative get methods. To use absolute get method, you need to pass index of the element you want to access. Relative get method returns the value relative to the current position.

		char deals[] = "best deals".toCharArray();
		CharBuffer cb = CharBuffer.wrap(deals);
		char c = cb.get(2);
		System.out.println(c);
		char d = cb.get();
		System.out.println(d);

Output:

s
b

To access different types of values, ByteBuffer offers methods such as getDouble, getChar, getInt, getDouble, etc.

To access bulk values, you can use relative and absolute get() methods passing array to it.

		char part[] = new char[10];
		cb.get(part);

Buffer object provides relative and absolute put() methods to add values to buffer. It also provides bulk relative put() methods to add array of values to it.

		IntBuffer ib = IntBuffer.allocate(40);
		ib.put(10);
		int vals[] = {2, 55, 34, 65};
		ib.put(vals);

If you call get() immediately after put, you will not get first character. In the below example, after put, position is set to 5. A call to get() on the buffer give empty char value.

		CharBuffer cbuff = CharBuffer.allocate(50);
		cbuff.put("deals");	
		System.out.println(cbuff.get());

To make the get() return first element, you need to set the position to 0. Subsequent get() calls return next char value that means get() call increases position. If you call get() after position reaches to limit, it will throw BufferUnderflowException.

		cbuff.put("deals");	
		cbuff.position(0);
		System.out.println(cbuff.get());
		System.out.println(cbuff.get());

Output:

d
e

In order to prevent getting empty values or to get only active values from the buffer, you can set limit to total active element in the buffer. Going back to our example, char buffer contains only 5 elements. So we can set limit to 5. To prevent BufferUnderflowException, you can hasRemaining() to check if next active value is available.

		CharBuffer cbuff = CharBuffer.allocate(50);
		cbuff.put("deals");	
		cbuff.limit(cbuff.position());
		cbuff.position(0);		
		while(cbuff.hasRemaining()) {
			System.out.println(cbuff.get());
		}

Instead of calling limit and position methods, you can use flip() method to exactly do the same. It is important to note that flip() call should not be called twice. Second call to flip() sets the limit to 0.

Buffer provides rewind() method, it just sets the position of buffer to 0 without resetting the limit property of the buffer. You can reuse buffer after it is filled and drained using clear() method. Clear method doesn’t remove elements from buffer, but it sets the current position to 0 and the limit property is assigned the value of capacity of the buffer.

Buffer provides slice() method using which you can create a new Buffer object that contains values from current position of source buffer. The elements are shared between source and the new buffer, meaning changes to common element in buffers will be visible in the other buffer object. Slicing buffers allows you to get part of a buffer and perform modifications on it without using complete content.

		char deals[] = "best deals".toCharArray();
		CharBuffer cb = CharBuffer.wrap(deals);
		cb.position(5);
		
		CharBuffer cbs = cb.slice();	
		System.out.println(cbs.toString());
		System.out.println(cb.toString());
		
		cbs.put(3, 'n');
		System.out.println(cbs.toString());
		System.out.println(cb.toString());

Output:

deals
deals
deans
deans

You can convert a buffer to read only buffer using asReadOnlyBuffer() method.

Character Set

Java new input output API provides in java.nio.charset package charset, character encoders and decoders which can be used to translate between bytes and Unicode characters.

CharSet is a name mapping between 16 bit Unicode characters and bytes.

Decoder transfers bytes in a specific character set into characters. Encode transfers characters into bytes in a specific character set. Standard character sets are US-ASCII (Seven-bit ASCII), ISO-8859-1 (ISO Latin alphabet No. 1), UTF-8 (8 bit character set unicode standard), UTF-16BE, UTF-16LE and UTF-16 (16 bit character set unicode standard) .

To get a Charset object, you need to call forName() static method of Charset class passing character set name.

Charset cs = Charset.forName("UTF-8");

You can get character set name and display name by calling name() and displayName() methods respectively on charset object. To encode a string using a specific char set, first create charset object using forName method and then call encode() method passing the string to be encoded. Method encode returns ByteBuffer object.

To decode bytes into characters, first create ByeBuffer containing the bytes, call decode() method on char set object passing ByteBuffer. Method decode() returns CharBuffer object.

		String deal = "upto 55% off on fashion";
		CharBuffer cbuff = CharBuffer.wrap(deal);
		
		Charset cs = Charset.forName("UTF-8");
		ByteBuffer bb = cs.encode(cbuff);
		
		CharBuffer cb = cs.decode(bb);
		System.out.println(new String(cb.array()));

Channels

Channel objects efficiently perform read and write operations on the destinations such as file, device, network socket and program components. To read data, you create a buffer and pass it to the read method of the channel object. To write data, you create a buffer object and pass it to write method of the channel object.

Channels can be used in blocking and non-blocking mode. In blocking mode operations return after completion only.

Some of the implementation classes of Channel interface are AsynchronousFileChannel, AsynchronousServerSocketChannel, AsynchronousSocketChannel, DatagramChannel, FileChannel, SelectableChannel, ServerSocketChannel and SocketChannel.

FileInputStream.getChannel(), FileOutputStream.getChannel(), and RandomAccessFile.getChannel() methods return FileChannel.

File Reading and Writing using Buffer and Channel Objects

Let’s see how to read from a channel and to write to the channel. For this example, we will use FileChannel.

First let’s see how to read data from a file. For that, we need to create Buffer object, then get FileChannel object from InputStreamReader and call read() method on FileChannel object passing ByteBuffer object.

	try {
		FileInputStream fis = new FileInputStream(file);
		FileChannel fc = fis.getChannel();
		
		ByteBuffer bb =  ByteBuffer.allocate(100);			
		
		while(fc.read(bb) > 0) {
			bb.rewind();
			String str = Charset.forName("UTF-8").decode(bb).toString();
			System.out.println(str);
			bb.flip();

		}			
		
	} catch (IOException e) {

	}

To write data to a file, get the FileChannel object from OutputStreamWriter and call write method on FileChannel object passing ByteBuffer object.

	try {
		FileOutputStream fos = new FileOutputStream(file);
		FileChannel fc = fos.getChannel();
		
		ByteBuffer bb =  ByteBuffer.allocate(1000);			

		String deal = "upto 10% off on fashion";
		bb.put(deal.getBytes());
		bb.flip();
		fc.write(bb);
		
		deal = ",upto 30% off on bags";
		bb.flip();
		bb.put(deal.getBytes());
		bb.flip();
		fc.write(bb);
	} catch (IOException e) {

	}

Non Blocking IO Using Selector and Selectable Channels

You can create IO components which are multiplexed and non blocking using selector, selectable channel and selection key. Selector class is a multiplexer which allows multiple selectable channels to be added to it allowing multiple channels to be used on a single thread. Selector allows you to select channels which are ready for IO operations.

The channels that can be added to Selector are special channels created for using with Selector. The selectable channels can be operated in blocking mode (every IO operation blocks until it is completed) and non blocking mode (non-blocking channel doesn’t block the thread). To use a selectable channel with the selector, you need to put the selectable channel in non blocking mode.

When a selectable channel is added to selector, a token called selection key representing the registration of a channel with selector is created. Selector adds the newly created key to a set. When a channel becomes ready for IO operation, selector adds the key to a set which contains only keys of channels which are ready for IO. You can access this set, by invoking selectedKeys() method on the selector object.

Here are the steps to be performed in creating multiplexed non blocking IO components.

  • First create a selectable channel. Selectable channel classes are DatagramChannel for data gram sockets, Pipe.SinkChannel represents readable end of pipe, Pipe.SourceChannel represents writable end of pipe, ServerSocketChannel for stream oriented listening sockets, SocketChannel for stream oriented connecting sockets.
  • Put the selectable channel in non blocking mode.
  • Create selector object by calling static method open() on Selector class. It creates selector object using default selector provider.
  • Then register the selectable channel with selector by calling register() method on the channel object passing selector object and operation interest such as SelectionKey.OP_ACCEPT, SelectionKey.OP_CONNECT, SelectionKey.OP_READ or SelectionKey.OP_WRITE. The register() method returns selection key.
  • Then repeatedly invoke select() method on the selector object to check if any channels are ready for IO operations. This method returns number of channels ready for IO operations.
  • If select method in the above step returns greater than zero, get selected keys by calling selectedKeys() method on the selector object.
  • Iterate over the selected keys and check to see if a key represents an interested operation. If a key represents an interested operation, then get the selectable channel associated with the key and perform the operation on the channel.
  • Remove the key from selected set by calling remove on iterator.

Non Blocking IO Server Example

Let’s see how to use selector, selectable channel and selection key by creating non blocking IO component. In this example, we will use ServerSocketChannel and SocketChannel to create multiplexing server which accepts client connections, processes request and sends response to client.

The example first creates ServerSocketChannel, registers with selector for accept operation and repeatedly checks for channel readiness. When there is a client request, it gets the SocketChannel from ServerSocketChannel and registers it with selector for read operations. It checks for readiness of SocketChannel for read operation, reads input from client and sends response.

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;

public class NonBlockingIOServer {
	
	private int port = 8080;
	private ByteBuffer bb = ByteBuffer.allocate(1000);

	public static void main(String[] args) {
		NonBlockingIOServer server = new NonBlockingIOServer();
		server.startServer();
	}
	public void startServer() {
		
		try {
			Selector selector = Selector.open();

			ServerSocketChannel ssc =  ServerSocketChannel.open();
			ssc.configureBlocking(false);

			ServerSocket ss = ssc.socket();			
			ss.bind(new InetSocketAddress(port));

			ssc.register(selector, SelectionKey.OP_ACCEPT);

			while(true) {
				System.out.println("waiting for client connection");
				if(selector.select() > 0) {
					performIO(selector);
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public void performIO(Selector s) {
		Iterator<SelectionKey> i = s.selectedKeys().iterator();

		while(i.hasNext()) {
			try {
				SelectionKey sk = i.next();
				if(sk.isAcceptable()) {
					System.out.println("accept client connection");
					acceptConnection(sk, s);
				}else if (sk.isReadable()) {
					System.out.println("read from client");
					readWriteClient(sk);
				}
			}catch (IOException e) {
				e.printStackTrace();
			}
			
			i.remove();
		}
	}
	public void acceptConnection(SelectionKey sk, Selector s) throws IOException{
		ServerSocketChannel server = (ServerSocketChannel) sk.channel();
		SocketChannel sChannel = server.accept();

		sChannel.configureBlocking(false);
		sChannel.register(s, SelectionKey.OP_READ);
	}
	public void readWriteClient(SelectionKey sk) throws IOException {		

		SocketChannel schannel = (SocketChannel) sk.channel();
		bb.flip();
		bb.clear();
		
		int count = schannel.read(bb);
		if (count > 0) {
			bb.flip();
			String input = Charset.forName("UTF-8").decode(bb).toString();
			System.out.println(input);
			
			bb.flip();
			bb.clear();
			bb.put(processClientRequest(input).getBytes());
			bb.flip();
			String inputs = Charset.forName("UTF-8").decode(bb).toString();
			System.out.println(inputs);	
			bb.rewind();
			schannel.write(bb);
		
			schannel.close();
		}
	}
	
	public String processClientRequest(String input) {
		if(input.startsWith("deals")) {
			return "upto 20% off on fashion";
		}else {
			return "invalid request";
		}
	}
}

You can run the program, it will start server and listens for requests on 8080 port. If you send deals string from client, it will respond with offer. Below is the output.

waiting for client connection
accept client connection
waiting for client connection
read from client
deals

Non Blocking IO Client Example

Below is the non blocking multiplexing client example. The example first creates SocketChannel, then connects to server, sends request to it and registers the channel with selector for read operations. Once the channel is ready for read operations, the response from server is captured.

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;

public class NonBlockingIOClient {
	private int port = 8080;
	private String hostName = "localhost";
	private ByteBuffer bb = ByteBuffer.allocate(1000);
	
	public static void main(String[] args) {
		NonBlockingIOClient client = new NonBlockingIOClient();
		client.getResponseFromServer("deals");
	}
	
	//main client method
	public void getResponseFromServer(String request) {
		try {
			//non blocking client socket
			SocketChannel sc = SocketChannel.open();
			sc.configureBlocking(false);

			InetSocketAddress addr = new InetSocketAddress(hostName, port);		 
			sc.connect(addr);			 

			while (!sc.finishConnect()) {
				System.out.println("conneting to server");
			}

			//send request
			bb.flip();
			bb.clear();
			bb.put(request.getBytes());
			bb.flip();
			sc.write(bb);

			//process response
			Selector selector = Selector.open();
			sc.register(selector, SelectionKey.OP_READ);		 
			while(true) {
				if(selector.select() > 0) {
					if(processServerResponse(selector)) {
						return;
					}
				}
			}
		}catch (IOException e) {
			e.printStackTrace();
		}
	}

	public boolean processServerResponse(Selector s) {		
		Iterator<SelectionKey> i = s.selectedKeys().iterator();
		while(i.hasNext()) {
			try {
				SelectionKey sk = i.next();
				if (sk.isReadable()) {
					SocketChannel schannel = (SocketChannel) sk.channel();
					bb.flip();
					bb.clear();

					int count = schannel.read(bb);
					if (count > 0) {
						bb.rewind();
						String response = 
						Charset.forName("UTF-8").decode(bb).toString();
						System.out.println("response: "+response);
						
						schannel.close();
						return true;
					}
				}
				i.remove();
			}catch (IOException e) {
				e.printStackTrace();
			}
		}
		return false;
	}
}

If you run this example, you will get response from the server started using above server example.

response: upto 20% off on fashion

Other Tutorials Related to IO