Master Java NIO: From Basics to High‑Performance I/O with Real‑World Examples
This article provides a comprehensive guide to Java NIO, covering its core components—Channel, Buffer, and Selector—explaining the differences from traditional IO, demonstrating file, socket, and server implementations with detailed code samples, and illustrating advanced features such as memory‑mapped files, scatter/gather, and pipe communication.
Overview
Java NIO (New I/O) introduces three core concepts: Channel , Buffer , and Selector . Unlike traditional stream‑oriented IO, NIO operates on buffers, allowing data to be read into a buffer from a channel or written from a buffer to a channel, and enabling a single thread to manage multiple channels.
Channel
A Channel is a bidirectional conduit comparable to a stream but capable of both reading and writing. The main implementations are FileChannel, DatagramChannel, SocketChannel, and ServerSocketChannel, corresponding to file IO, UDP, and TCP (client/server) respectively.
Buffer
Buffers are containers for primitive data types (e.g., ByteBuffer, CharBuffer, IntBuffer). Typical usage follows these steps:
Allocate space (e.g., ByteBuffer buf = ByteBuffer.allocate(1024)).
Read data into the buffer ( channel.read(buf)).
Flip the buffer ( buf.flip()) to prepare for reading.
Read data from the buffer ( buf.get()).
Clear or compact the buffer for further writes.
Buffers maintain capacity , position , limit , and mark to track state.
Selector
A Selector allows a single thread to monitor multiple channels for events such as connection acceptance, data arrival, or readiness to write, eliminating the need for busy‑waiting loops.
Typical workflow:
Create a selector: Selector selector = Selector.open(); Register channels with interest sets (e.g., OP_ACCEPT, OP_READ).
Call selector.select() (or select(timeout)) to block until events occur.
Iterate over selected keys, handling each event (accept, read, write, connect).
FileChannel Example (Traditional IO vs NIO)
public static void method2() {
InputStream in = null;
try {
in = new BufferedInputStream(new FileInputStream("src/nomal_io.txt"));
byte[] buf = new byte[1024];
int bytesRead = in.read(buf);
while (bytesRead != -1) {
for (int i = 0; i < bytesRead; i++) {
System.out.print((char) buf[i]);
}
bytesRead = in.read(buf);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try { if (in != null) in.close(); } catch (IOException e) { e.printStackTrace(); }
}
} public static void method1() {
RandomAccessFile aFile = null;
try {
aFile = new RandomAccessFile("src/nio.txt", "rw");
FileChannel fileChannel = aFile.getChannel();
ByteBuffer buf = ByteBuffer.allocate(1024);
int bytesRead = fileChannel.read(buf);
System.out.println(bytesRead);
while (bytesRead != -1) {
buf.flip();
while (buf.hasRemaining()) {
System.out.print((char) buf.get());
}
buf.compact();
bytesRead = fileChannel.read(buf);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try { if (aFile != null) aFile.close(); } catch (IOException e) { e.printStackTrace(); }
}
}SocketChannel Example (Client)
public static void client() {
ByteBuffer buffer = ByteBuffer.allocate(1024);
SocketChannel socketChannel = null;
try {
socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress("10.10.195.115", 8080));
if (socketChannel.finishConnect()) {
int i = 0;
while (true) {
TimeUnit.SECONDS.sleep(1);
String info = "I'm " + i++ + "-th information from client";
buffer.clear();
buffer.put(info.getBytes());
buffer.flip();
while (buffer.hasRemaining()) {
socketChannel.write(buffer);
}
}
}
} catch (IOException | InterruptedException e) {
e.printStackTrace();
} finally {
try { if (socketChannel != null) socketChannel.close(); } catch (IOException e) { e.printStackTrace(); }
}
}ServerSocketChannel & Selector Example (TCP Server)
public static void selector() {
Selector selector = null;
ServerSocketChannel ssc = null;
try {
selector = Selector.open();
ssc = ServerSocketChannel.open();
ssc.socket().bind(new InetSocketAddress(8080));
ssc.configureBlocking(false);
ssc.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
if (selector.select(3000) == 0) continue;
Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
while (iter.hasNext()) {
SelectionKey key = iter.next();
if (key.isAcceptable()) handleAccept(key);
if (key.isReadable()) handleRead(key);
if (key.isWritable() && key.isValid()) handleWrite(key);
iter.remove();
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try { if (selector != null) selector.close(); } catch (IOException e) { e.printStackTrace(); }
try { if (ssc != null) ssc.close(); } catch (IOException e) { e.printStackTrace(); }
}
}
private static void handleAccept(SelectionKey key) throws IOException {
ServerSocketChannel ssChannel = (ServerSocketChannel) key.channel();
SocketChannel sc = ssChannel.accept();
sc.configureBlocking(false);
sc.register(key.selector(), SelectionKey.OP_READ, ByteBuffer.allocateDirect(1024));
}
private static void handleRead(SelectionKey key) throws IOException {
SocketChannel sc = (SocketChannel) key.channel();
ByteBuffer buf = (ByteBuffer) key.attachment();
long bytesRead = sc.read(buf);
while (bytesRead > 0) {
buf.flip();
while (buf.hasRemaining()) System.out.print((char) buf.get());
System.out.println();
buf.clear();
bytesRead = sc.read(buf);
}
if (bytesRead == -1) sc.close();
}
private static void handleWrite(SelectionKey key) throws IOException {
ByteBuffer buf = (ByteBuffer) key.attachment();
buf.flip();
SocketChannel sc = (SocketChannel) key.channel();
while (buf.hasRemaining()) sc.write(buf);
buf.compact();
}Memory‑Mapped Files (MappedByteBuffer)
For large files, MappedByteBuffer offers superior performance by mapping a file directly into virtual memory.
public static void method3() {
RandomAccessFile aFile = null;
FileChannel fc = null;
try {
aFile = new RandomAccessFile("src/1.ppt", "rw");
fc = aFile.getChannel();
long start = System.currentTimeMillis();
MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_ONLY, 0, aFile.length());
long end = System.currentTimeMillis();
System.out.println("Read time: " + (end - start) + "ms");
} catch (IOException e) { e.printStackTrace(); }
finally { try { if (aFile != null) aFile.close(); if (fc != null) fc.close(); } catch (IOException e) { e.printStackTrace(); } }
}Benchmark results show MappedByteBuffer reading a 5 MB file in ~2 ms versus ~12 ms with a regular ByteBuffer, and the gap widens dramatically for larger files.
Note: A mapped byte buffer remains valid until it is garbage‑collected, which means the underlying file may stay open longer than expected.
Other NIO Features
Scatter/Gather : Read from a channel into multiple buffers (scatter) or write from multiple buffers into a channel (gather).
transferFrom / transferTo : Directly transfer bytes between channels without intermediate buffers.
Pipe : One‑way data connection between two threads (source and sink channels).
DatagramChannel : UDP communication using send/receive methods.
These utilities enable efficient, non‑blocking, and scalable I/O operations suitable for high‑performance server applications.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Programmer DD
A tinkering programmer and author of "Spring Cloud Microservices in Action"
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
