Hello there and welcome to my new article on Java Programming. Today we will talk about Java NIO which stands for New I/O. Going forward we will talk about some of the useful classes and use of foundational components – Channels and Buffers.

The Path Interface

Being one of the most important addition to NIO, it encapsulates a File location in a given file system. The operations defined on Path interface doesn’t interact with the actual file system. To get the real path encapsulated by the Path object, we have to call toRealPath() method which will throw IOException if the the actual file doesn’t exist in the file system. We cannot instantiate a Path object directly, for this we have to use one of the static methods from Paths class. Take a look at the below given code and its output.

import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
/**
* @author Seyed Sahil
*/
public class Main {
public static void main(String[] args) {
String fileName = "movies.txt";
Path filePath = Paths.get(fileName);
System.out.println("Path '" + filePath + "' is " + (filePath.isAbsolute() ? "absolute" : "relative"));
try {
Path realFilePath = filePath.toRealPath();
System.out.println("Path '" + realFilePath + "' is " + (realFilePath.isAbsolute() ? "absolute" :
"relative"));
} catch (IOException ex) {
ex.printStackTrace(System.err);
}
}
}
Path 'movies.txt' is relative
Path 'C:\Users\seyed\Home\My Space\Development\Research\image-clean\movies.txt' is absolute

The Files Class

The Files class contain methods which is used to perform several operations pertaining to a File. The File to be acted upon is specified using its Path. Some of these operations are create, delete, move copy etc. and it also got other methods which tells us the attribute level information. Take a look at the below given code and its output.

package org.sydlabz;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
/**
* @author Seyed Sahil
*/
public class Main {
public static void main(String[] args) throws IOException {
String sourceName = "source", targetName = "target", oldPackagesName = "older";
String packageName;
// Copy package1.txt from source to target
packageName = "package1.txt";
Files.copy(Paths.get(sourceName + File.separator + packageName), Paths.get(targetName + File.separator + packageName),
StandardCopyOption.REPLACE_EXISTING);
System.out.println("File '" + packageName + "' copied from '" + sourceName + "' to '" + targetName + "'");
// Delete package1.txt from source
Files.delete(Paths.get(sourceName + File.separator + packageName));
System.out.println("File '" + packageName + "' deleted from '" + sourceName + "'");
// Move package2.txt from target to older (creates older directory befre move)
Files.createDirectory(Paths.get(targetName + File.separator + oldPackagesName));
System.out.println("Directory '" + oldPackagesName + "' created in '" + targetName + "'");
packageName = "package2.txt";
Files.move(Paths.get(targetName + File.separator + packageName),
Paths.get(targetName + File.separator + oldPackagesName + File.separator + packageName), StandardCopyOption.REPLACE_EXISTING);
System.out.println("File '" + packageName + "' moved from '" + targetName + "' to '" + targetName + File.separator + oldPackagesName +
"'");
}
}
File 'package1.txt' copied from 'source' to 'target'
File 'package1.txt' deleted from 'source'
Directory 'older' created in 'target'
File 'package2.txt' moved from 'target' to 'target\older'

The operations mentioned in the above given program interacts with the file systems and so they will throw exceptions if there is any bad operations.

Channels and Buffers

An open connection to an I/O device is called Channel and Buffer is used to hold the data. Here, a perfect example of I/O device is a file. To the base a Buffer is defined by three properties – current position, limit and capacity where current position indicates the position to which next read / write operation happen. Capacity indicates the maximum elements a Buffer can hold and most of the time limit will be N where N stands for the index of first element that should not be read or written. It is always possible to change the limit of a buffer and not cannot be changed beyond capacity.

In Java, several objects supports channels and they are FileInputStream, FileOutputStream, Socket etc. and to get the channel we have to call getChannel() method from these objects. Channels are auto closeable and also provide us with methods that gives us access and control over the channel.

Data is read from a Channel into a Buffer and written from a Buffer to a Channel.

Read File Contents…

Lets see how to make use of channels and buffers to read contents from a file. Here in the below given program I have included few of the scenarios with comments. The file otp.txt contains a long chain of digits in random order.

package org.sydlabz;
import java.io.IOException;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
/**
* @author Seyed Sahil
*/
public class Main {
public static void main(String[] args) throws IOException {
Path otpFilePath = Paths.get("otp.txt");
// Allocating a byte buffer of capacity 12, by default capacity and limit will be same.
ByteBuffer otpBuffer = ByteBuffer.allocate(12);
printBuffer("After allocation", otpBuffer);
try (SeekableByteChannel otpFileChannel = Files.newByteChannel(otpFilePath)) {
System.out.println("Stage 1");
// Reading data from file to buffer. This will change the position of buffer.
int numberOfBytes = otpFileChannel.read(otpBuffer);
System.out.println("Number of bytes read from file is '" + numberOfBytes + "'");
printBuffer("After loading data", otpBuffer);
// Rewind is required to read data from buffer. This will reset the position back to zero.
otpBuffer.rewind();
printBuffer("After rewind", otpBuffer);
// Reading 6 digits from the buffer for generating the OTP. This will change the position of buffer.
byte[] nextOtp = new byte[otpBuffer.capacity() / 2];
otpBuffer.get(nextOtp);
System.out.println("New OTP '" + new String(nextOtp) + "' created.");
printBuffer("After reading OTP", otpBuffer);
System.out.println("Stage 2");
// Flip will change the limit of buffer to position of buffer and set position of buffer back to zero.
otpBuffer.flip();
printBuffer("After flip", otpBuffer);
otpBuffer.get(nextOtp);
System.out.println("New OTP '" + new String(nextOtp) + "' created.");
printBuffer("After reading OTP", otpBuffer);
// The following three lines of code wll throw exception, because we are trying to read beyond the limits.
// otpBuffer.get(nextOtp);
// System.out.println("New OTP '" + new String(nextOtp) + "' created.");
// printBuffer("After reading OTP", otpBuffer);
System.out.println("Stage 3");
// Setting the limit back to capacity and this let us read the remaining buffer.
otpBuffer.limit(otpBuffer.capacity());
printBuffer("After increasing limit", otpBuffer);
otpBuffer.get(nextOtp);
System.out.println("New OTP '" + new String(nextOtp) + "' created.");
printBuffer("After reading OTP", otpBuffer);
System.out.println("Stage 4");
// Here the number of bytes read from file will be zero here.
// This is because the position of buffer is at limit which is 12.
numberOfBytes = otpFileChannel.read(otpBuffer);
System.out.println("After loading data to buffer, caret position in file is '" + numberOfBytes + "'");
printBuffer("After new file read", otpBuffer);
// Rewind the buffer to set position bvack to zero
otpBuffer.rewind();
printBuffer("After rewind", otpBuffer);
otpBuffer.get(nextOtp);
System.out.println("New OTP '" + new String(nextOtp) + "' created.");
printBuffer("After reading OTP", otpBuffer);
// Flip the limits and position.
otpBuffer.flip();
printBuffer("After flip", otpBuffer);
// Now the limit is half of the capacity of buffer, the number of bytes read should be 6.
numberOfBytes = otpFileChannel.read(otpBuffer);
System.out.println("Number of bytes read from file is '" + numberOfBytes + "'");
printBuffer("After new file read", otpBuffer);
// We should get a fresh OTP here.
otpBuffer.rewind();
otpBuffer.get(nextOtp);
System.out.println("New OTP '" + new String(nextOtp) + "' created.");
printBuffer("After reading OTP", otpBuffer);
}
}
private static void printBuffer(String message, Buffer buffer) {
System.out.println(message + " – Buffer[capacity=" + buffer.capacity() + ", limit=" + buffer.limit() + ", " +
"position=" + buffer.position() + "]");
}
}
 After allocation - Buffer[capacity=12, limit=12, position=0]
 Stage 1
 Number of bytes read from file is '12'
 After loading data - Buffer[capacity=12, limit=12, position=12]
 After rewind - Buffer[capacity=12, limit=12, position=0]
 New OTP '123456' created.
 After reading OTP - Buffer[capacity=12, limit=12, position=6]
 Stage 2
 After flip - Buffer[capacity=12, limit=6, position=0]
 New OTP '123456' created.
 After reading OTP - Buffer[capacity=12, limit=6, position=6]
 Stage 3
 After increasing limit - Buffer[capacity=12, limit=12, position=6]
 New OTP '768900' created.
 After reading OTP - Buffer[capacity=12, limit=12, position=12]
 Stage 4
 After loading data to buffer, caret position in file is '0'
 After new file read - Buffer[capacity=12, limit=12, position=12]
 After rewind - Buffer[capacity=12, limit=12, position=0]
 New OTP '123456' created.
 After reading OTP - Buffer[capacity=12, limit=12, position=6]
 After flip - Buffer[capacity=12, limit=6, position=0]
 Number of bytes read from file is '6'
 After new file read - Buffer[capacity=12, limit=6, position=6]
 New OTP '856784' created.
 After reading OTP - Buffer[capacity=12, limit=6, position=6]

Yeah, this one is a bity lengthy program and here I tried to include various operations and behavior of Buffer as well as Channel upon executing different operations.

With this the introduction to Java NIO is complete.

Thank You

Seyed Sahil