Java NIO: Channels & Buffers
Java NIO (New I/O, introduced in Java 1.4) provides channels and buffers as an alternative to the classic stream-based I/O. Channels represent open connections to I/O devices (files, sockets). Buffers are fixed-size memory blocks that data passes through. The combination enables high-throughput file operations via FileChannel, and non-blocking network I/O via SocketChannel and Selector.
Contents
ByteBuffer Basics
A ByteBuffercapacitylimitpositionflip()
import java.nio.ByteBuffer;
// Allocate a heap buffer
ByteBuffer buf = ByteBuffer.allocate(1024);
// Write bytes
buf.put("Hello NIO".getBytes());
// Flip: limit = current position, position = 0
buf.flip();
// Read bytes
byte[] bytes = new byte[buf.limit()];
buf.get(bytes);
System.out.println(new String(bytes)); // Hello NIO
// Rewind: position = 0, limit unchanged (re-read)
buf.rewind();
// Clear: position = 0, limit = capacity (ready to write again)
buf.clear();
// Direct buffer — allocated outside the Java heap (faster for I/O)
ByteBuffer direct = ByteBuffer.allocateDirect(4096);
ByteBuffer.allocateDirect()FileChannel: Reading & Writing
Open a FileChannelRandomAccessFileFileInputStreamFileChannel.open()ByteBuffer
import java.nio.channels.FileChannel;
import java.nio.file.*;
// Writing to a file
Path path = Path.of("output.bin");
try (FileChannel fc = FileChannel.open(path,
StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {
ByteBuffer buf = ByteBuffer.allocate(64);
buf.putInt(42);
buf.putDouble(3.14);
buf.flip(); // prepare buffer for reading by the channel
fc.write(buf);
}
// Reading from a file
try (FileChannel fc = FileChannel.open(path, StandardOpenOption.READ)) {
ByteBuffer buf = ByteBuffer.allocate(64);
int bytesRead = fc.read(buf); // fills buffer; returns -1 at EOF
buf.flip(); // prepare buffer for reading by our code
int intVal = buf.getInt();
double dblVal = buf.getDouble();
System.out.printf("int=%d double=%.2f%n", intVal, dblVal);
}
FileChannel.transferTo / transferFrom
transferTo()transferFrom()
Path src = Path.of("source.dat");
Path dest = Path.of("destination.dat");
try (FileChannel from = FileChannel.open(src, StandardOpenOption.READ);
FileChannel to = FileChannel.open(dest, StandardOpenOption.CREATE,
StandardOpenOption.WRITE)) {
long size = from.size();
long transferred = 0;
while (transferred < size) {
transferred += from.transferTo(transferred, size - transferred, to);
}
System.out.println("Copied " + transferred + " bytes");
}
transferTo()sendfile()Scatter / Gather I/O
Scatter reads fill multiple buffers in one system call; gather writes drain multiple buffers in one call. Useful for reading structured messages with distinct header and body regions.
// Scatter read — fill header buffer then body buffer
ByteBuffer header = ByteBuffer.allocate(16);
ByteBuffer body = ByteBuffer.allocate(1024);
ByteBuffer[] buffers = { header, body };
try (FileChannel fc = FileChannel.open(Path.of("message.bin"), StandardOpenOption.READ)) {
long bytesRead = fc.read(buffers); // scatter: fills header first, then body
header.flip();
body.flip();
int msgType = header.getInt();
int bodyLen = header.getInt();
// … read body bytes …
}
// Gather write — write header + body in one call
header.clear();
header.putInt(1); // message type
header.putInt(64); // body length
header.flip();
body.clear();
body.put("payload data".getBytes());
body.flip();
try (FileChannel fc = FileChannel.open(Path.of("out.bin"),
StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {
fc.write(buffers); // gather: writes header then body
}
SocketChannel: Non-blocking Client
Put a SocketChannel
import java.nio.channels.SocketChannel;
import java.net.InetSocketAddress;
SocketChannel channel = SocketChannel.open();
channel.configureBlocking(false);
boolean connected = channel.connect(new InetSocketAddress("example.com", 80));
// In non-blocking mode connect() may return false — finish via finishConnect()
while (!channel.finishConnect()) {
// do other work or spin
}
// Send an HTTP request
String request = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n";
ByteBuffer buf = ByteBuffer.wrap(request.getBytes());
channel.write(buf);
// Read response
ByteBuffer response = ByteBuffer.allocate(4096);
int read;
StringBuilder sb = new StringBuilder();
while ((read = channel.read(response)) > 0) {
response.flip();
sb.append(new String(response.array(), 0, read));
response.clear();
}
System.out.println(sb);
channel.close();
ServerSocketChannel & Selector
A Selectorselect()
import java.nio.channels.*;
import java.net.InetSocketAddress;
import java.util.Iterator;
ServerSocketChannel server = ServerSocketChannel.open();
server.configureBlocking(false);
server.bind(new InetSocketAddress(9090));
Selector selector = Selector.open();
server.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("Server listening on port 9090");
while (true) {
selector.select(); // blocks until at least one channel is ready
Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
while (keys.hasNext()) {
SelectionKey key = keys.next();
keys.remove();
if (key.isAcceptable()) {
// Accept new connection
SocketChannel client = server.accept();
client.configureBlocking(false);
client.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {
// Read from client
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer buf = ByteBuffer.allocate(256);
int bytesRead = client.read(buf);
if (bytesRead == -1) {
client.close();
} else {
buf.flip();
client.write(buf); // echo back
}
}
}
}
Memory-Mapped Files
Map a file (or a region of it) directly into the JVM's address space. Access is then as fast as reading an in-memory array: the OS handles loading pages from disk on demand.
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.*;
Path path = Path.of("large-data.bin");
try (FileChannel fc = FileChannel.open(path, StandardOpenOption.READ, StandardOpenOption.WRITE)) {
// Map the entire file into memory
MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, 0, fc.size());
// Random-access reads and writes — no system call per access
mbb.position(1024);
int value = mbb.getInt();
mbb.position(2048);
mbb.putInt(value * 2);
// Force pending changes to disk (optional — OS flushes eventually)
mbb.force();
}