1. Introduction to Java NIO
Difference between Java IO vs Java NIO
- Java IO (Stream API)
- Stream-oriented (read/write one byte/character at a time).
- Blocking I/O – thread waits until data is available.
- Simple, easier for small-scale applications.
- Java NIO (New IO)
- Buffer-oriented (data is read into a buffer, then processed).
- Non-blocking I/O – threads can do other work while waiting for data.
- Uses Channels, Buffers, and Selectors → efficient for large-scale or high-performance apps.
Synchronous vs Asynchronous I/O
- Synchronous I/O
- Data transfer happens step-by-step.
- A thread makes a request and waits until it completes.
- Example: traditional Java IO and NIO in blocking mode.
- Asynchronous I/O
- The request is initiated, but the thread doesn’t wait—it continues execution.
- Completion is notified via callbacks/events.
- Example: Java NIO.2 (introduced in Java 7) supports async channels (AsynchronousSocketChannel, AsynchronousFileChannel).
Buffer-oriented, Channel-based Architecture
- Buffer
- A container for data.
- Data is read from a Channel into a Buffer, and written from a Buffer into a Channel.
- Types: ByteBuffer, CharBuffer, IntBuffer, etc.
- Channel
- A bi-directional data connection between a data source (like a file, socket) and a buffer.
- Types: FileChannel, SocketChannel, DatagramChannel, ServerSocketChannel.
- Selector
- Allows a single thread to monitor multiple channels for events (connect, read, write).
- Useful in scalable, non-blocking I/O applications (e.g., chat servers).
2. Buffers in Java NIO
Concept of Buffers
- A Buffer is a container for data.
- Unlike Java IO (which directly reads/writes data), NIO uses Buffers to hold data temporarily.
- Data flow:
Channel → Buffer → Process → Buffer → Channel - Each buffer has three key properties:
- Capacity – total size of buffer (fixed at creation).
- Position – index where the next read/write happens.
- Limit – index one past the last element that can be read/written.
Types of Buffers
All buffer classes extend the abstract java.nio.Buffer class.
They are typed buffers for different data types:
- ByteBuffer – holds byte data, most commonly used (base for other buffers).
- CharBuffer – holds char data (Unicode characters).
- ShortBuffer – holds short data.
- IntBuffer – holds int data.
- LongBuffer – holds long data.
- FloatBuffer – holds float data.
- DoubleBuffer – holds double data.
These allow handling primitive data efficiently without converting to objects.
Important Buffer Methods
- flip()
- Switches from writing mode to reading mode.
- Sets limit = position and position = 0.
- Example: after writing into buffer, call flip() before reading.
- clear()
- Clears the buffer for writing again.
- position = 0, limit = capacity.
- Doesn’t erase data, just marks it as overwritten-ready.
- rewind()
- Resets position = 0.
- Allows re-reading the data without clearing.
- mark() and reset()
- mark() → sets a checkpoint at current position.
- reset() → returns position back to the last mark.
- Useful for re-processing data.
Direct vs Non-Direct Buffers
- Direct Buffer
- Allocated outside JVM heap (in OS memory).
- Faster for I/O operations (no copying between JVM and OS).
- Created using ByteBuffer.allocateDirect(size).
- Best for high-performance networking, file channels.
- Non-Direct Buffer
- Allocated inside JVM heap.
- Easier to manage, but slightly slower for I/O.
- Created using ByteBuffer.allocate(size).
Quick Example (ByteBuffer usage):
import java.nio.*;
public class BufferExample {
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(10); // Non-direct buffer
// Writing data
buffer.put((byte) 65); // ASCII 'A'
buffer.put((byte) 66); // ASCII 'B'
buffer.flip(); // Switch to reading mode
// Reading data
while (buffer.hasRemaining()) {
System.out.println((char) buffer.get());
}
buffer.clear(); // Ready for new write
}
}
Output:
A
B
3. Channels in Java NIO
Concept of Channels
- A Channel is like a stream but with extra power:
- Bidirectional → Can read and write data using the same channel.
- Works with Buffers (instead of byte-by-byte transfer).
- Channels connect a data source (file, socket, datagram) to a Buffer.
- They are more flexible, efficient, and scalable than traditional IO streams.
Flow of data:
Channel <----> Buffer <----> Program
Types of Channels
- FileChannel
- Reads/writes data from/to files.
- Supports random access (can read/write at specific positions).
- Created using:
FileChannel channel = new FileInputStream("data.txt").getChannel();
2. DatagramChannel
- For UDP connections.
- Can send/receive datagrams (packets).
Example:
DatagramChannel dc = DatagramChannel.open(); dc.connect(new InetSocketAddress("localhost", 9999));
3. SocketChannel
- For TCP client connections.
- Used to connect to a remote server.
- Example:
SocketChannel sc = SocketChannel.open(new InetSocketAddress("localhost", 8080));
4. ServerSocketChannel
- For TCP server connections.
- Listens for incoming requests and creates SocketChannel for each client.
- Example:
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.bind(new InetSocketAddress(8080));
Opening, Closing, and Reading/Writing with Channels
- Opening a Channel
- Use factory methods (open()) or get from stream (getChannel()).
- Closing a Channel
channel.close();
3. Reading/Writing with Channels
- Always use Buffers.
- Example with FileChannel:
RandomAccessFile file = new RandomAccessFile("data.txt", "rw");
FileChannel channel = file.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(48);
int bytesRead = channel.read(buffer); // Reading
while (bytesRead != -1) {
buffer.flip(); // Switch to read mode
while (buffer.hasRemaining()) {
System.out.print((char) buffer.get());
}
buffer.clear(); // Ready for next write
bytesRead = channel.read(buffer);
}
channel.close();
file.close();
Channel-to-Channel Data Transfer
- Channels allow direct data transfer between channels without intermediate buffers (very efficient).
- transferFrom()
- Copies data from one channel into another.
destinationChannel.transferFrom(sourceChannel, position, count);
2. transferTo()
- Sends data directly from one channel to another.
sourceChannel.transferTo(position, count, destinationChannel);
Example (Copy file using transfer):
FileChannel source = new FileInputStream("source.txt").getChannel();
FileChannel dest = new FileOutputStream("dest.txt").getChannel();
dest.transferFrom(source, 0, source.size());
source.close();
dest.close();
4. Selectors in Java NIO
What is a Selector?
- A Selector is a Java NIO component that allows a single thread to monitor multiple channels (sockets/files) simultaneously.
- Instead of dedicating one thread per connection, you can use one thread for many connections → more scalable and efficient.
- Selector monitors channels for events (like incoming data, readiness to write, new client connections).
This is the backbone of non-blocking I/O and high-performance servers (like chat servers, HTTP servers, NIO-based frameworks).
Non-blocking I/O with Selectors
- Channels must be put into non-blocking mode before registering with a selector.
channel.configureBlocking(false);
- This way, I/O operations don’t block the thread — the selector checks which channels are ready and processes them.
Registering Channels with Selectors
- Open a selector:
Selector selector = Selector.open();
2. Register a channel with the selector:
channel.register(selector, SelectionKey.OP_READ);
Here, we say: “Hey selector, wake me up when this channel is ready for reading.”
Selection Keys
When a channel is registered with a selector, a SelectionKey is returned.
It represents the relationship between the selector and the channel.
Available interest sets (operations we can monitor):
- SelectionKey.OP_READ → Channel is ready for reading.
- SelectionKey.OP_WRITE → Channel is ready for writing.
- SelectionKey.OP_CONNECT → A connection was established (client side).
- SelectionKey.OP_ACCEPT → A new client connection is ready to be accepted (server side).
Multiplexed, Scalable I/O with Selectors
- Instead of looping through many channels, selector.select() blocks until at least one channel is ready.
- Then you can handle multiple connections efficiently in a single thread.
Example (basic selector usage for a server):
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
public class SelectorExample {
public static void main(String[] args) throws IOException {
// Open server socket channel
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.bind(new InetSocketAddress(8080));
serverChannel.configureBlocking(false);
// Open selector
Selector selector = Selector.open();
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
ByteBuffer buffer = ByteBuffer.allocate(256);
while (true) {
selector.select(); // Blocks until channel is ready
Iterator keys = selector.selectedKeys().iterator();
while (keys.hasNext()) {
SelectionKey key = keys.next();
keys.remove();
if (key.isAcceptable()) {
SocketChannel client = serverChannel.accept();
client.configureBlocking(false);
client.register(selector, SelectionKey.OP_READ);
}
else if (key.isReadable()) {
SocketChannel client = (SocketChannel) key.channel();
buffer.clear();
int bytesRead = client.read(buffer);
if (bytesRead == -1) {
client.close();
} else {
buffer.flip();
client.write(buffer);
}
}
}
}
}
}
This is a non-blocking echo server:
- Accepts clients,
- Reads data,
- Writes it back,
- All managed by one thread using Selector.
5. File I/O with NIO
1. Reading and Writing Files with FileChannel
- FileChannel provides efficient file reading and writing using buffers.
- You can open it via FileInputStream, FileOutputStream, or RandomAccessFile.
Example – Reading from file using FileChannel
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class FileChannelExample {
public static void main(String[] args) throws Exception {
RandomAccessFile file = new RandomAccessFile("data.txt", "rw");
FileChannel channel = file.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(64);
int bytesRead = channel.read(buffer);
while (bytesRead != -1) {
buffer.flip(); // switch to read mode
while (buffer.hasRemaining()) {
System.out.print((char) buffer.get());
}
buffer.clear();
bytesRead = channel.read(buffer);
}
channel.close();
file.close();
}
}
Example – Writing to file using FileChannel
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class FileWriteExample {
public static void main(String[] args) throws Exception {
FileOutputStream fos = new FileOutputStream("output.txt");
FileChannel channel = fos.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(64);
buffer.put("Hello NIO FileChannel!".getBytes());
buffer.flip();
channel.write(buffer);
channel.close();
}
}
2. Memory-Mapped Files (MappedByteBuffer)
- A memory-mapped file allows you to map a region of a file directly into memory.
- Very fast because OS handles paging between memory and file.
- Useful for large files (e.g., 1GB+), as you don’t need to load the whole file into JVM heap.
Example – Memory-Mapped File
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
public class MemoryMappedExample {
public static void main(String[] args) throws Exception {
RandomAccessFile file = new RandomAccessFile("largefile.txt", "rw");
FileChannel channel = file.getChannel();
// Map first 1 MB of the file into memory
MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, 1024 * 1024);
buffer.put(0, (byte) 'H'); // Modify file directly in memory
buffer.put(1, (byte) 'i');
channel.close();
file.close();
}
}
3. File Locking (FileLock)
- Used to safely access files across multiple processes/threads.
- Supports:
- Exclusive Lock → only one process can access.
- Shared Lock → multiple readers, no writers.
Example – File Lock
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
public class FileLockExample {
public static void main(String[] args) throws Exception {
RandomAccessFile file = new RandomAccessFile("locked.txt", "rw");
FileChannel channel = file.getChannel();
// Acquire an exclusive lock
FileLock lock = channel.lock();
System.out.println("File locked. Performing safe write...");
// Release lock
lock.release();
channel.close();
file.close();
}
}
4. Asynchronous FileChannel (Java 7+)
- AsynchronousFileChannel enables non-blocking file operations.
- Useful for high-performance servers that don’t want threads blocked on file reads/writes.
- Works with callbacks (CompletionHandler) or Futures.
Example – Asynchronous Read
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.concurrent.Future;
public class AsyncFileExample {
public static void main(String[] args) throws IOException {
AsynchronousFileChannel channel =
AsynchronousFileChannel.open(Paths.get("async.txt"), StandardOpenOption.READ);
ByteBuffer buffer = ByteBuffer.allocate(1024);
Future result = channel.read(buffer, 0);
while (!result.isDone()) {
System.out.println("Doing something else while file is read...");
}
buffer.flip();
while (buffer.hasRemaining()) {
System.out.print((char) buffer.get());
}
channel.close();
}
}
6. Path, Files, and FileSystem (Java NIO.2 – Java 7+)
1. Path Interface (Replacement of File)
- Introduced in java.nio.file package.
- Path replaces the older java.io.File for more flexibility.
- Represents a hierarchical path in the filesystem.
- Created using Paths utility class:
import java.nio.file.*;
Path path1 = Paths.get("data.txt"); // relative path
Path path2 = Paths.get("C:/Users/Muskan/file"); // absolute path
2. Operations on Path
- resolve(Path other) → joins two paths.
Path base = Paths.get("C:/data");
Path full = base.resolve("file.txt"); // C:/data/file.txt
- relativize(Path other) → creates a relative path from one path to another.
Path p1 = Paths.get("C:/data");
Path p2 = Paths.get("C:/data/logs/file.txt");
System.out.println(p1.relativize(p2)); // logs/file.txt
- normalize() → removes redundant . and ...
Path p = Paths.get("C:/data/../logs/./file.txt");
System.out.println(p.normalize()); // C:/logs/file.txt
3. The Files Utility Class
- Provides many static methods for file operations.
Common operations:
copy()
Files.copy(Paths.get("source.txt"), Paths.get("dest.txt"));
move()
Files.move(Paths.get("file.txt"), Paths.get("backup/file.txt"));
delete()
Files.delete(Paths.get("oldfile.txt"));
- exists()
if (Files.exists(Paths.get("data.txt"))) { ... }
- createFile() / createDirectory()
Files.createFile(Paths.get("newfile.txt"));
Files.createDirectory(Paths.get("newfolder"));
- walkFileTree() → recursive traversal of a directory.
Uses FileVisitor interface for custom actions.
Files.walkFileTree(Paths.get("C:/projects"), new SimpleFileVisitor() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { System.out.println("Visited: " + file); return FileVisitResult.CONTINUE; } });
4. File Attributes
- NIO.2 allows reading OS-specific file attributes.
- BasicFileAttributes → common attributes (creationTime, size, lastModifiedTime).
- DosFileAttributes → Windows-specific (hidden, read-only, archive).
- PosixFileAttributes → Unix/Linux-specific (owner, group, permissions).
Example:
Path path = Paths.get("data.txt"); BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class); System.out.println("Creation Time: " + attrs.creationTime()); System.out.println("Last Modified: " + attrs.lastModifiedTime()); System.out.println("Size: " + attrs.size());
5. FileSystem Class
- Represents the filesystem itself (like NTFS, ext4, FAT32, ZIP).
- Obtained from FileSystems class.
Example:
FileSystem fs = FileSystems.getDefault(); for (Path root : fs.getRootDirectories()) { System.out.println("Root: " + root); }
Can also open ZIP/JAR files as file systems:
FileSystem zipFs = FileSystems.newFileSystem(Paths.get("archive.zip"), null);
5. File I/O with NIO
Java NIO introduced channels and buffers as a more efficient and flexible way to handle file input/output compared to the traditional stream-based I/O.
1. Reading and Writing Files with FileChannel
- FileChannel is a channel that connects to a file and allows both reading and writing.
- It can be obtained from:
- FileInputStream.getChannel()
- FileOutputStream.getChannel()
- RandomAccessFile.getChannel()
- Key Features:
- Works with ByteBuffer.
- Supports random access (read/write at specific positions).
- Can transfer data directly between channels (transferTo, transferFrom).
Example:
RandomAccessFile file = new RandomAccessFile("data.txt", "rw"); FileChannel channel = file.getChannel(); ByteBuffer buffer = ByteBuffer.allocate(64); buffer.put("Hello NIO!".getBytes()); buffer.flip(); channel.write(buffer); // Write data channel.close(); file.close();
2. Memory-Mapped Files (MappedByteBuffer)
- Allows a file to be mapped into memory.
- Changes made in the buffer are reflected directly in the file.
- Very fast for large files, as the OS handles paging in/out.
- How to create:
RandomAccessFile file = new RandomAccessFile("data.txt", "rw");
FileChannel channel = file.getChannel();
// Map first 1KB of file into memory
MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, 1024);
buffer.put(0, (byte)65); // Writes 'A' at position 0
channel.close();
file.close();
3. File Locking (FileLock)
- NIO provides file locking to prevent multiple processes from modifying the same file simultaneously.
- Types of locks:
- Exclusive lock – only one process can access.
- Shared lock – multiple readers allowed, but no writer.
Example:
RandomAccessFile file = new RandomAccessFile("data.txt", "rw");
FileChannel channel = file.getChannel();
FileLock lock = channel.lock(); // Exclusive lock
System.out.println("File locked...");
// Work with file
lock.release(); // Unlock
channel.close();
file.close();
4. AsynchronousFileChannel (Java 7+)
- Provides non-blocking file I/O.
- Useful when working with large files or server applications.
- Uses Future or CompletionHandler for async results.
Example with Future:
Path path = Paths.get("data.txt");
AsynchronousFileChannel asyncChannel =
AsynchronousFileChannel.open(path, StandardOpenOption.READ);
ByteBuffer buffer = ByteBuffer.allocate(1024);
Future result = asyncChannel.read(buffer, 0);
while (!result.isDone()) {
System.out.println("Reading asynchronously...");
}
buffer.flip();
while (buffer.hasRemaining()) {
System.out.print((char) buffer.get());
}
asyncChannel.close();
6. Path, Files, and FileSystem (Java NIO.2 – Java 7+)
Java 7 introduced NIO.2, a major enhancement in file and directory operations, replacing many limitations of the old java.io.File.
1. Path Interface (replacement of File)
- Represents the location of a file or directory in the filesystem.
- More powerful and flexible than File.
- Created using Paths.get() or FileSystems.getDefault().getPath().
Example:
Path path = Paths.get("C:/Users/Muskan/Documents/file.txt");
System.out.println("File name: " + path.getFileName());
System.out.println("Parent: " + path.getParent());
System.out.println("Root: " + path.getRoot());
2. Operations on Path
The Path interface provides many methods to manipulate paths.
- resolve() → Combines paths.
Path base = Paths.get("C:/data");
Path child = base.resolve("file.txt"); // C:/data/file.txt
- relativize() → Creates a relative path between two paths.
Path p1 = Paths.get("C:/data");
Path p2 = Paths.get("C:/data/sub/file.txt");
System.out.println(p1.relativize(p2)); // sub/file.txt
normalize() → Removes redundant elements (. and ..).
Path path = Paths.get("C:/data/./sub/../file.txt"); System.out.println(path.normalize()); // C:/data/file.txt
3. The Files Utility Class
- The java.nio.file.Files class provides static utility methods for common file operations.
- Copy
Files.copy(Paths.get("source.txt"), Paths.get("dest.txt"), StandardCopyOption.REPLACE_EXISTING);
Move (rename)
Files.move(Paths.get("old.txt"), Paths.get("new.txt"), StandardCopyOption.REPLACE_EXISTING);
- Delete
Files.delete(Paths.get("unwanted.txt"));
Check existence
System.out.println(Files.exists(Paths.get("data.txt")));
Create
Files.createFile(Paths.get("newfile.txt")); Files.createDirectory(Paths.get("newfolder"));
walkFileTree() → Traverse a directory tree recursively.
Files.walkFileTree(Paths.get("C:/data"), new SimpleFileVisitor() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { System.out.println("File: " + file); return FileVisitResult.CONTINUE; } });
4. File Attributes
- NIO.2 provides APIs to read file metadata.
- BasicFileAttributes → Common attributes like size, creation time, last modified time.
- DosFileAttributes → For DOS/Windows systems (hidden, read-only, archive).
- PosixFileAttributes → For UNIX/Linux systems (owner, group, permissions).
- Example:
Path path = Paths.get("data.txt");
BasicFileAttributes attr = Files.readAttributes(path, BasicFileAttributes.class);
System.out.println("Creation time: " + attr.creationTime());
System.out.println("Size: " + attr.size());
5. FileSystem Class
- Represents the filesystem itself.
- Useful for working with different file systems (e.g., ZIP, custom FS).
- FileSystems.getDefault() → Current operating system FS.
- Can open a ZIP file as a filesystem.
Example:
FileSystem fs = FileSystems.getDefault();
for (FileStore store : fs.getFileStores()) {
System.out.println("Drive: " + store);
}
Path path = fs.getPath("C:/Users");
System.out.println("Path: " + path);
7. Asynchronous I/O (Java 7+)
Java 7 introduced Asynchronous Channels in the java.nio.channels package.
Unlike synchronous I/O (which blocks until completion), asynchronous I/O allows a program to initiate an operation and continue doing other tasks, while the operation runs in the background.
1. Asynchronous Channels
Asynchronous channels extend the concept of Channel by supporting non-blocking, asynchronous operations.
(a) AsynchronousFileChannel
- Reads/writes files asynchronously.
- Uses Future or CompletionHandler for results.
- Useful for large file operations.
Example (Future-based read):
Path path = Paths.get("data.txt");
AsynchronousFileChannel channel =
AsynchronousFileChannel.open(path, StandardOpenOption.READ);
ByteBuffer buffer = ByteBuffer.allocate(1024);
Future result = channel.read(buffer, 0);
while (!result.isDone()) {
System.out.println("Reading asynchronously...");
}
buffer.flip();
while (buffer.hasRemaining()) {
System.out.print((char) buffer.get());
}
channel.close();
(b) AsynchronousSocketChannel
- Enables client-side asynchronous socket communication.
- Useful for high-performance networking apps.
Example:
AsynchronousSocketChannel client = AsynchronousSocketChannel.open();
client.connect(new InetSocketAddress("localhost", 5000)).get();
ByteBuffer buffer = ByteBuffer.wrap("Hello Server".getBytes());
client.write(buffer).get();
client.close();
(c) AsynchronousServerSocketChannel
- Used for non-blocking server sockets.
- Accepts client connections asynchronously.
Example:
AsynchronousServerSocketChannel server =
AsynchronousServerSocketChannel.open()
.bind(new InetSocketAddress(5000));
server.accept(null, new CompletionHandler() {
@Override
public void completed(AsynchronousSocketChannel client, Void att) {
System.out.println("Client connected!");
// Accept next client
server.accept(null, this);
}
@Override
public void failed(Throwable exc, Void att) {
System.out.println("Connection failed: " + exc.getMessage());
}
});
2. Completion Handlers & Futures
Asynchronous operations can notify completion in two ways:
- Future → Poll or block until result is ready.
Future future = channel.read(buffer, 0);
CompletionHandler → Callback invoked when operation finishes (non-blocking, event-driven).
channel.read(buffer, 0, buffer, new CompletionHandler() { @Override public void completed(Integer result, ByteBuffer attachment) { System.out.println("Read bytes: " + result); } @Override public void failed(Throwable exc, ByteBuffer attachment) { System.out.println("Read failed: " + exc.getMessage()); } });
3. Thread Pools in Asynchronous I/O
- Asynchronous channels internally use a thread pool to perform background operations.
- By default, they use the system-wide thread pool (ForkJoinPool.commonPool() or cached thread pool).
- Custom thread pools can be specified using AsynchronousChannelGroup.
- Example with custom thread pool:
ExecutorService executor = Executors.newFixedThreadPool(4);
AsynchronousChannelGroup group =
AsynchronousChannelGroup.withThreadPool(executor);
AsynchronousServerSocketChannel server =
AsynchronousServerSocketChannel.open(group).bind(new InetSocketAddress(5000));
8. Character Sets and Encoders/Decoders
When working with files or network communication, data is often exchanged as bytes, but humans work with characters (letters, symbols).
Java NIO provides the Charset API to handle encoding (chars → bytes) and decoding (bytes → chars).
1. Charset Class
- Represents a mapping between characters and bytes.
- Found in java.nio.charset.
- Provides methods to:
- Encode: Convert CharBuffer → ByteBuffer.
- Decode: Convert ByteBuffer → CharBuffer.
- Common charsets: UTF-8, ISO-8859-1, US-ASCII, UTF-16.
Example:
Charset charset = Charset.forName("UTF-8");
System.out.println("Charset: " + charset.displayName());
2. Encoding and Decoding
Encoding → characters → bytes.
Decoding → bytes → characters.
Example (Encoding and Decoding):
Charset charset = Charset.forName("UTF-8");
String text = "नमस्ते"; // Hindi text
ByteBuffer byteBuffer = charset.encode(text); // Encoding
System.out.println("Encoded bytes: " + Arrays.toString(byteBuffer.array()));
CharBuffer charBuffer = charset.decode(byteBuffer); // Decoding
System.out.println("Decoded text: " + charBuffer.toString());
3. Working with Different Charsets
Different systems or files may use different encodings.
Java provides standard and platform-dependent charsets.
- UTF-8 → Universal, supports all characters (multibyte).
- ISO-8859-1 (Latin-1) → Western European languages (single byte per char).
- US-ASCII → 7-bit basic English.
- UTF-16 → Uses 2 bytes per character.
Example:
String text = "Hello World!";
byte[] utf8 = text.getBytes(StandardCharsets.UTF_8);
byte[] iso = text.getBytes(StandardCharsets.ISO_8859_1);
System.out.println("UTF-8: " + Arrays.toString(utf8));
System.out.println("ISO-8859-1: " + Arrays.toString(iso));
9. Scatter/Gather I/O
Scatter/Gather I/O is a powerful feature of Java NIO Channels that allows splitting or combining data efficiently using multiple buffers.
1. Scatter Read
- Reads data from a single channel into multiple buffers.
- Data is distributed in sequence among the buffers.
- First buffer gets filled, then the next, and so on.
Example:
RandomAccessFile file = new RandomAccessFile("data.txt", "r");
FileChannel channel = file.getChannel();
ByteBuffer header = ByteBuffer.allocate(10);
ByteBuffer body = ByteBuffer.allocate(50);
ByteBuffer[] buffers = { header, body };
channel.read(buffers); // Scatter Read
header.flip();
body.flip();
System.out.println("Header: " + new String(header.array()));
System.out.println("Body: " + new String(body.array()));
channel.close();
file.close();
2. Gather Write
- Writes data from multiple buffers into a single channel.
- Buffers are written in sequence, one after another.
- Useful when combining structured data.
Example:
RandomAccessFile file = new RandomAccessFile("output.txt", "rw");
FileChannel channel = file.getChannel();
ByteBuffer header = ByteBuffer.wrap("HeaderData".getBytes());
ByteBuffer body = ByteBuffer.wrap("This is the body.".getBytes());
ByteBuffer[] buffers = { header, body };
channel.write(buffers); // Gather Write
channel.close();
file.close();
3. Use Cases in Network Programming & Performance Optimization
- Network Protocols → Example: In HTTP, one buffer can store headers, another buffer can store the body (scatter read). When sending, write both in sequence (gather write).
- Performance Optimization → Avoids copying and concatenating data into one buffer before sending.
- Structured Data Handling → Different parts of structured messages can be handled separately (headers, metadata, payload).
10. Socket Programming with NIO
Java NIO provides a non-blocking client-server model, which is more scalable than traditional blocking sockets (java.net).
1. Non-blocking Client-Server Model
- Traditional sockets (Socket/ServerSocket) block until data arrives or connection is made.
- With NIO, sockets can run in non-blocking mode:
- Client tries to connect but doesn’t block.
- Server can handle multiple clients simultaneously without creating a thread per client.
- This is achieved using Channels + Selectors.
2. SocketChannel (Client)
- Used for client connections.
- Can be set to non-blocking mode.
- Works with ByteBuffer for reading/writing.
Example (Client):
SocketChannel client = SocketChannel.open();
client.configureBlocking(false);
client.connect(new InetSocketAddress("localhost", 5000));
while (!client.finishConnect()) {
System.out.println("Waiting to connect...");
}
ByteBuffer buffer = ByteBuffer.wrap("Hello Server".getBytes());
client.write(buffer);
client.close();
3. ServerSocketChannel (Server)
- Listens for incoming client connections.
- Can be non-blocking.
- Accepts new connections and returns a SocketChannel for communication.
Example (Server):
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.bind(new InetSocketAddress(5000));
serverChannel.configureBlocking(false);
while (true) {
SocketChannel client = serverChannel.accept(); // Non-blocking
if (client != null) {
System.out.println("New client connected: " + client.getRemoteAddress());
ByteBuffer buffer = ByteBuffer.wrap("Welcome Client!".getBytes());
client.write(buffer);
}
}
4. Using Selectors for Handling Multiple Client Connections
- A Selector monitors multiple channels.
- Instead of looping over clients, the server uses one thread to handle many clients.
- Each channel registers with the selector for events:
- OP_ACCEPT → A new client connection is ready.
- OP_READ → Data is ready to read.
- OP_WRITE → Channel is ready to write.
Example with Selector:
Selector selector = Selector.open();
ServerSocketChannel server = ServerSocketChannel.open();
server.bind(new InetSocketAddress(5000));
server.configureBlocking(false);
// Register server for accept events
server.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
selector.select(); // Blocking until events occur
Set keys = selector.selectedKeys();
Iterator it = keys.iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
if (key.isAcceptable()) {
SocketChannel client = server.accept();
client.configureBlocking(false);
client.register(selector, SelectionKey.OP_READ);
System.out.println("Client connected: " + client.getRemoteAddress());
}
else if (key.isReadable()) {
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(256);
client.read(buffer);
System.out.println("Received: " + new String(buffer.array()).trim());
}
it.remove();
}
}
Conclusion
Java NIO provides a modern, efficient, and scalable alternative to traditional I/O in Java. Unlike the old stream-based model, NIO introduces buffers, channels, and selectors, making data transfer faster, more flexible, and suitable for high-performance applications. With features like memory-mapped files, asynchronous channels, scatter/gather I/O, and zero-copy transfers, NIO significantly reduces CPU overhead and improves throughput.
The NIO.2 enhancements in Java 7, such as the Path, Files, and FileSystem APIs, file watching services, and asynchronous I/O support, make file and network operations more powerful and developer-friendly.
Overall, Java NIO is best suited for applications that demand high concurrency, scalability, and low-latency performance—such as servers, file transfer systems, and network-based applications—while still offering the flexibility to work seamlessly with character sets, encoders/decoders, and symbolic links.