Java NIO (New I/O) in Java

Java NIO (New I/O) is an alternative I/O API introduced in Java 1.4 that provides high-speed, block-oriented I/O capabilities. It's designed to be more efficient than traditional Java I/O for certain use cases.

1. NIO vs Traditional I/O

Key Differences

Traditional I/OJava NIO
Stream-orientedBuffer-oriented
Blocking I/ONon-blocking I/O
SynchronousAsynchronous
One thread per connectionOne thread can handle multiple connections

2. Core Components of NIO

Basic Imports

import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.DatagramChannel;
import java.nio.file.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.io.IOException;
import java.util.EnumSet;

3. Buffers

Buffers are the fundamental data structure in NIO used for reading and writing data.

Buffer Types

  • ByteBuffer
  • CharBuffer
  • ShortBuffer
  • IntBuffer
  • LongBuffer
  • FloatBuffer
  • DoubleBuffer

Buffer Properties

  • Capacity: Maximum number of elements buffer can hold
  • Position: Current position for reading/writing
  • Limit: First element that should not be read/written
  • Mark: Remembered position for reset

Basic Buffer Operations

public class BufferExample {
public static void main(String[] args) {
// Create a ByteBuffer with capacity of 1024 bytes
ByteBuffer buffer = ByteBuffer.allocate(1024);
System.out.println("Initial state:");
printBufferInfo(buffer);
// Write data to buffer
String data = "Hello, NIO!";
buffer.put(data.getBytes());
System.out.println("\nAfter putting data:");
printBufferInfo(buffer);
// Flip buffer for reading
buffer.flip();
System.out.println("\nAfter flip (ready for reading):");
printBufferInfo(buffer);
// Read data from buffer
byte[] readData = new byte[buffer.remaining()];
buffer.get(readData);
System.out.println("Read data: " + new String(readData));
System.out.println("\nAfter reading:");
printBufferInfo(buffer);
// Clear buffer for reuse
buffer.clear();
System.out.println("\nAfter clear:");
printBufferInfo(buffer);
}
private static void printBufferInfo(ByteBuffer buffer) {
System.out.println("Position: " + buffer.position());
System.out.println("Limit: " + buffer.limit());
System.out.println("Capacity: " + buffer.capacity());
System.out.println("Remaining: " + buffer.remaining());
}
}

Direct vs Non-Direct Buffers

public class DirectBufferExample {
public static void main(String[] args) {
// Non-direct buffer (allocated in JVM heap)
ByteBuffer heapBuffer = ByteBuffer.allocate(1024);
System.out.println("Is direct: " + heapBuffer.isDirect());
// Direct buffer (allocated in OS memory)
ByteBuffer directBuffer = ByteBuffer.allocateDirect(1024);
System.out.println("Is direct: " + directBuffer.isDirect());
// Direct buffers are faster for I/O operations but
// more expensive to allocate and deallocate
}
}

Buffer View Operations

public class BufferViews {
public static void main(String[] args) {
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
// Write some data
for (int i = 0; i < 10; i++) {
byteBuffer.put((byte) i);
}
byteBuffer.flip();
// Create read-only view
ByteBuffer readOnlyBuffer = byteBuffer.asReadOnlyBuffer();
System.out.println("Read-only: " + readOnlyBuffer.isReadOnly());
// Create duplicate
ByteBuffer duplicateBuffer = byteBuffer.duplicate();
// Create slice (shares portion of original buffer)
byteBuffer.position(2);
byteBuffer.limit(6);
ByteBuffer sliceBuffer = byteBuffer.slice();
System.out.println("Original capacity: " + byteBuffer.capacity());
System.out.println("Slice capacity: " + sliceBuffer.capacity());
}
}

4. Channels

Channels represent connections to entities capable of performing I/O operations.

FileChannel Examples

Reading from a File

public class FileChannelReadExample {
public static void main(String[] args) {
Path path = Paths.get("test.txt");
try (FileChannel channel = FileChannel.open(path, StandardOpenOption.READ)) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (channel.read(buffer) > 0) {
buffer.flip(); // Prepare buffer for reading
// Process data
while (buffer.hasRemaining()) {
System.out.print((char) buffer.get());
}
buffer.clear(); // Prepare buffer for next read
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

Writing to a File

public class FileChannelWriteExample {
public static void main(String[] args) {
Path path = Paths.get("output.txt");
String data = "Hello, Java NIO!\nThis is a test file.";
try (FileChannel channel = FileChannel.open(
path, 
StandardOpenOption.CREATE, 
StandardOpenOption.WRITE,
StandardOpenOption.TRUNCATE_EXISTING)) {
ByteBuffer buffer = ByteBuffer.wrap(data.getBytes());
channel.write(buffer);
System.out.println("Data written successfully");
} catch (IOException e) {
e.printStackTrace();
}
}
}

File Copy with Transfer

public class FileCopyExample {
public static void main(String[] args) {
Path sourcePath = Paths.get("source.txt");
Path destPath = Paths.get("destination.txt");
try (FileChannel sourceChannel = FileChannel.open(sourcePath, StandardOpenOption.READ);
FileChannel destChannel = FileChannel.open(destPath, 
StandardOpenOption.CREATE, 
StandardOpenOption.WRITE,
StandardOpenOption.TRUNCATE_EXISTING)) {
// Efficient file transfer (uses OS-level zero-copy when possible)
long position = 0;
long size = sourceChannel.size();
while (position < size) {
position += sourceChannel.transferTo(position, size - position, destChannel);
}
System.out.println("File copied successfully");
} catch (IOException e) {
e.printStackTrace();
}
}
}

SocketChannel Examples

Non-blocking Client

public class NIOClient {
public static void main(String[] args) {
try (SocketChannel socketChannel = SocketChannel.open()) {
// Configure non-blocking mode
socketChannel.configureBlocking(false);
// Connect to server
socketChannel.connect(new InetSocketAddress("localhost", 8080));
// Wait for connection to complete
while (!socketChannel.finishConnect()) {
System.out.println("Still connecting...");
Thread.sleep(1000);
}
// Send data
String message = "Hello from NIO Client!";
ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());
while (buffer.hasRemaining()) {
socketChannel.write(buffer);
}
System.out.println("Message sent to server");
// Read response
buffer.clear();
StringBuilder response = new StringBuilder();
while (socketChannel.read(buffer) > 0) {
buffer.flip();
while (buffer.hasRemaining()) {
response.append((char) buffer.get());
}
buffer.clear();
}
System.out.println("Server response: " + response.toString());
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
}

5. Selectors

Selectors allow a single thread to handle multiple channels.

Server with Selector

public class NIOServer {
private Selector selector;
private ServerSocketChannel serverChannel;
public void start(int port) throws IOException {
// Create selector
selector = Selector.open();
// Create server socket channel
serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
serverChannel.bind(new InetSocketAddress(port));
// Register server channel with selector for accept events
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("Server started on port " + port);
// Event loop
while (true) {
// Wait for events (blocks until at least one event occurs)
int readyChannels = selector.select();
if (readyChannels == 0) continue;
// Process selected keys
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if (key.isAcceptable()) {
acceptConnection(key);
} else if (key.isReadable()) {
readData(key);
} else if (key.isWritable()) {
writeData(key);
}
keyIterator.remove();
}
}
}
private void acceptConnection(SelectionKey key) throws IOException {
ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
SocketChannel clientChannel = serverChannel.accept();
clientChannel.configureBlocking(false);
// Register client channel for read operations
clientChannel.register(selector, SelectionKey.OP_READ);
System.out.println("Client connected: " + clientChannel.getRemoteAddress());
}
private void readData(SelectionKey key) throws IOException {
SocketChannel clientChannel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int bytesRead = clientChannel.read(buffer);
if (bytesRead == -1) {
// Connection closed
clientChannel.close();
System.out.println("Client disconnected");
return;
}
if (bytesRead > 0) {
buffer.flip();
byte[] data = new byte[buffer.remaining()];
buffer.get(data);
String message = new String(data);
System.out.println("Received: " + message);
// Echo back the message
ByteBuffer response = ByteBuffer.wrap(("Echo: " + message).getBytes());
clientChannel.write(response);
}
}
private void writeData(SelectionKey key) throws IOException {
// Handle write operations
SocketChannel clientChannel = (SocketChannel) key.channel();
// Write logic here
}
public static void main(String[] args) {
try {
new NIOServer().start(8080);
} catch (IOException e) {
e.printStackTrace();
}
}
}

6. java.nio.file Package

Path Operations

public class PathOperations {
public static void main(String[] args) {
// Creating Path objects
Path path1 = Paths.get("/home/user/documents/file.txt");
Path path2 = Paths.get("C:", "Users", "John", "file.txt");
Path path3 = Paths.get("relative/path/file.txt");
// Path information
System.out.println("File name: " + path1.getFileName());
System.out.println("Parent: " + path1.getParent());
System.out.println("Root: " + path1.getRoot());
System.out.println("Name count: " + path1.getNameCount());
// Path navigation
Path subPath = path1.subpath(1, 3);
System.out.println("Subpath: " + subPath);
// Resolving paths
Path base = Paths.get("/base");
Path relative = Paths.get("subdir/file.txt");
Path resolved = base.resolve(relative);
System.out.println("Resolved: " + resolved);
// Normalizing paths
Path withDots = Paths.get("/home/./user/../john/file.txt");
Path normalized = withDots.normalize();
System.out.println("Normalized: " + normalized);
}
}

Files Utility Class

public class FilesExamples {
public static void main(String[] args) throws IOException {
Path source = Paths.get("source.txt");
Path target = Paths.get("target.txt");
Path directory = Paths.get("testdir");
// Create directory
Files.createDirectories(directory);
// Create file with content
Files.writeString(source, "Hello, Java NIO Files!", 
StandardOpenOption.CREATE);
// Copy file
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
// Read file content
String content = Files.readString(target);
System.out.println("File content: " + content);
// File attributes
System.out.println("Size: " + Files.size(target) + " bytes");
System.out.println("Last modified: " + Files.getLastModifiedTime(target));
System.out.println("Is readable: " + Files.isReadable(target));
// List directory contents
Files.createDirectories(Paths.get("testdir/subdir"));
Files.writeString(Paths.get("testdir/file1.txt"), "File 1");
Files.writeString(Paths.get("testdir/file2.txt"), "File 2");
try (Stream<Path> stream = Files.list(directory)) {
stream.forEach(System.out::println);
}
// Walk directory tree
System.out.println("\nWalking directory tree:");
Files.walk(directory)
.forEach(System.out::println);
}
}

File Watching Service

public class FileWatcherExample {
public static void main(String[] args) throws IOException, InterruptedException {
WatchService watchService = FileSystems.getDefault().newWatchService();
Path directory = Paths.get(".");
// Register for create, modify, and delete events
directory.register(watchService, 
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_MODIFY,
StandardWatchEventKinds.ENTRY_DELETE);
System.out.println("Watching directory: " + directory.toAbsolutePath());
while (true) {
WatchKey key = watchService.take(); // Blocks until event occurs
for (WatchEvent<?> event : key.pollEvents()) {
WatchEvent.Kind<?> kind = event.kind();
Path fileName = (Path) event.context();
System.out.println(kind + ": " + fileName);
if (kind == StandardWatchEventKinds.OVERFLOW) {
continue;
}
}
// Reset the key to receive further events
boolean valid = key.reset();
if (!valid) {
break;
}
}
}
}

7. Character Sets and Encoding

public class CharsetExample {
public static void main(String[] args) {
String text = "Hello, 世界!";
// Available charsets
System.out.println("Available charsets:");
Charset.availableCharsets().keySet().stream()
.sorted()
.forEach(System.out::println);
// Encoding and decoding
Charset utf8 = StandardCharsets.UTF_8;
Charset iso = StandardCharsets.ISO_8859_1;
// Encode string to bytes
ByteBuffer utf8Buffer = utf8.encode(text);
ByteBuffer isoBuffer = iso.encode(text);
System.out.println("\nUTF-8 bytes: " + Arrays.toString(utf8Buffer.array()));
System.out.println("ISO-8859-1 bytes: " + Arrays.toString(isoBuffer.array()));
// Decode bytes to string
utf8Buffer.rewind();
isoBuffer.rewind();
String decodedUtf8 = utf8.decode(utf8Buffer).toString();
String decodedIso = iso.decode(isoBuffer).toString();
System.out.println("Decoded UTF-8: " + decodedUtf8);
System.out.println("Decoded ISO-8859-1: " + decodedIso);
// Character buffer operations
CharBuffer charBuffer = CharBuffer.allocate(100);
charBuffer.put("Hello, NIO!");
charBuffer.flip();
ByteBuffer byteBuffer = utf8.encode(charBuffer);
System.out.println("Encoded bytes: " + byteBuffer.remaining());
}
}

8. Memory-Mapped Files

public class MemoryMappedFileExample {
public static void main(String[] args) throws IOException {
Path filePath = Paths.get("largefile.dat");
// Create a large file for demonstration
try (FileChannel channel = FileChannel.open(filePath, 
StandardOpenOption.CREATE, 
StandardOpenOption.READ, 
StandardOpenOption.WRITE)) {
// Create a 100MB file
long fileSize = 100 * 1024 * 1024; // 100MB
channel.position(fileSize - 1);
channel.write(ByteBuffer.wrap(new byte[1]));
// Map entire file to memory
MappedByteBuffer mappedBuffer = channel.map(
FileChannel.MapMode.READ_WRITE, 0, fileSize);
// Write data directly to memory
for (int i = 0; i < 1000; i++) {
mappedBuffer.putInt(i);
}
// Read data from memory
mappedBuffer.flip();
for (int i = 0; i < 10; i++) {
System.out.println("Read: " + mappedBuffer.getInt());
}
// Force changes to disk
mappedBuffer.force();
} catch (IOException e) {
e.printStackTrace();
}
}
}

9. Asynchronous File Channel

public class AsyncFileChannelExample {
public static void main(String[] args) throws IOException, ExecutionException, InterruptedException {
Path path = Paths.get("async_test.txt");
// Create file and write some content
Files.writeString(path, "Initial content\n", StandardOpenOption.CREATE);
try (AsynchronousFileChannel asyncChannel = AsynchronousFileChannel.open(
path, 
StandardOpenOption.READ, 
StandardOpenOption.WRITE)) {
// Asynchronous write
String data = "Hello, Async NIO!\n";
ByteBuffer writeBuffer = ByteBuffer.wrap(data.getBytes());
Future<Integer> writeResult = asyncChannel.write(writeBuffer, 0);
// Do other work while writing...
System.out.println("Doing other work while writing...");
// Wait for write to complete
Integer bytesWritten = writeResult.get();
System.out.println("Bytes written: " + bytesWritten);
// Asynchronous read
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
Future<Integer> readResult = asyncChannel.read(readBuffer, 0);
// Do other work while reading...
System.out.println("Doing other work while reading...");
Integer bytesRead = readResult.get();
readBuffer.flip();
byte[] readData = new byte[bytesRead];
readBuffer.get(readData);
System.out.println("Read data: " + new String(readData));
} catch (IOException e) {
e.printStackTrace();
}
}
}

10. Best Practices and Performance Tips

1. Use Direct Buffers for Large I/O Operations

// Good for large files
ByteBuffer buffer = ByteBuffer.allocateDirect(8192);
// Good for small, frequent allocations
ByteBuffer buffer = ByteBuffer.allocate(1024);

2. Reuse Buffers

public class BufferPool {
private final Queue<ByteBuffer> bufferPool = new ConcurrentLinkedQueue<>();
public ByteBuffer getBuffer(int size) {
ByteBuffer buffer = bufferPool.poll();
if (buffer == null || buffer.capacity() < size) {
return ByteBuffer.allocate(size);
}
buffer.clear();
return buffer;
}
public void returnBuffer(ByteBuffer buffer) {
buffer.clear();
bufferPool.offer(buffer);
}
}

3. Proper Buffer Management

public class BufferManagement {
public static void processFile(Path path) throws IOException {
try (FileChannel channel = FileChannel.open(path, StandardOpenOption.READ)) {
ByteBuffer buffer = ByteBuffer.allocateDirect(8192);
while (channel.read(buffer) != -1) {
buffer.flip(); // Switch to read mode
processBuffer(buffer);
buffer.compact(); // Keep unread data and prepare for next read
}
// Process any remaining data
buffer.flip();
if (buffer.hasRemaining()) {
processBuffer(buffer);
}
}
}
private static void processBuffer(ByteBuffer buffer) {
while (buffer.hasRemaining()) {
// Process each byte
byte b = buffer.get();
// ... processing logic
}
}
}

Summary

Java NIO Advantages:

  • Non-blocking I/O for better scalability
  • Memory-mapped files for high-performance file access
  • Selectors for handling multiple channels with single thread
  • Buffer-oriented operations for efficient data handling

When to Use NIO:

  • High-performance server applications
  • Large file processing
  • Applications requiring non-blocking I/O
  • Network applications with many concurrent connections

When to Use Traditional I/O:

  • Simple file operations
  • Sequential processing
  • When simplicity is more important than performance
  • Small-scale applications

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper