Understanding Java NIO vs IO: Channels, Buffers, and Netty Comparison
This article explains the core differences between Java NIO and traditional IO, introduces channels and buffers, details NIO’s underlying mechanisms, provides a complete code example, and compares NIO’s workflow with Netty’s model, highlighting performance and architectural distinctions.
First, the core differences:
NIO processes data in blocks, while traditional IO uses basic byte streams, making NIO generally much more efficient.
NIO no longer relies on InputStream/OutputStream; instead it uses channels and buffers built on top of streams.
NIO channels are bidirectional, whereas IO streams are unidirectional.
NIO buffers (essentially byte arrays) support slicing and can be read‑only, direct, or indirect; direct buffers are allocated specially to accelerate I/O.
The fundamental distinction between NIO and classic BIO is that NIO uses a multiplexed (non‑blocking) I/O model, while traditional IO is blocking.
First understand what channels and buffers are
What a channel means
A channel simulates the stream concept from the original I/O package; all data to any destination must pass through a Channel object. A Buffer is essentially a container object. Data sent to a channel must first be placed into a Buffer, and data read from a channel is read into a Buffer.
All data is processed via Buffer objects; you never write bytes directly to a channel, but write them into a Buffer that may contain one or more bytes. Likewise, you read bytes from a channel into a Buffer before retrieving them.
What a buffer means
A Buffer is an object that holds data to be written or that has just been read. In NIO, Buffer objects highlight a key difference from stream‑oriented I/O, where data is written directly to or read directly from a Stream.
All data in NIO is handled through buffers. When reading, data is read directly into a Buffer; when writing, data is first placed into a Buffer.
A Buffer is essentially an array—usually a byte array, though other array types exist. It provides structured access to data and can track the system’s read/write progress.
Buffer types
ByteBuffer
CharBuffer
ShortBuffer
IntBuffer
LongBuffer
FloatBuffer
DoubleBuffer
NIO underlying working principle
First, understand the buffer's working mechanism:
capacity – total length of the buffer array
position – index of the next element to be operated on
limit – index of the first element that should not be accessed (limit ≤ capacity)
mark – remembers a previous position (default 0)
When the buffer is first allocated, its state is as shown in the first diagram:
After writing a few bytes, the position moves to the next free slot. To transfer the data to a channel, you call buffer.flip();:
After calling buffer.flip();, the buffer’s state becomes position → limit. Then you call clear(); to reset:
Now the operating system can correctly read the 5 bytes from the buffer and send them out. Before the next write, you call clear() again, which resets the buffer indices to their initial positions (similar to flushing a char[] buf = new char[1024]; in classic IO).
Supplement
1. mark() records the position before the current one; reset() restores the position to the marked value.
2. clear() empties the entire buffer, setting position to 0 and limit to capacity (after flip() you have remembered how many bytes were written, and clear() copies that range to the channel).
3. When writing buffer data to a channel, you call flip() to switch from write mode to read mode; flip() sets position to 0 and limit to the previous position, effectively remembering how much data is buffered.
NIO working code example
public void selector() throws IOException {
// Allocate buffer memory
ByteBuffer buffer = ByteBuffer.allocate(1024);
// Open Selector to poll each Channel's state
Selector selector = Selector.open();
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.configureBlocking(false); // non‑blocking
ssc.socket().bind(new InetSocketAddress(8080));
ssc.register(selector, SelectionKey.OP_ACCEPT); // register accept event
while (true) {
Set selectedKeys = selector.selectedKeys(); // get all keys
Iterator it = selectedKeys.iterator();
while (it.hasNext()) {
SelectionKey key = (SelectionKey) it.next();
if ((key.readyOps() & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT) {
ServerSocketChannel ssChannel = (ServerSocketChannel) key.channel();
SocketChannel sc = ssChannel.accept(); // accept client request
sc.configureBlocking(false);
sc.register(selector, SelectionKey.OP_READ);
it.remove();
} else if ((key.readyOps() & SelectionKey.OP_READ) == SelectionKey.OP_READ) {
SocketChannel sc = (SocketChannel) key.channel();
while (true) {
buffer.clear();
int n = sc.read(buffer); // read data
if (n <= 0) {
break;
}
buffer.flip();
}
it.remove();
}
}
}
}Finally, an overall NIO diagram:
NIO and Netty work model comparison
(1) NIO workflow steps
Create a ServerSocketChannel and a thread pool for business processing.
Bind the ServerSocketChannel to a port and set it to non‑blocking.
Open a Selector and register the ServerSocketChannel with it, listening for OP_ACCEPT events.
The Selector enters a loop, calling select() to poll ready Channels.
When a key is OP_ACCEPT, accept the new client via ServerSocketChannel.accept().
Register the new client’s SocketChannel for OP_READ and set it non‑blocking, then remove the processed key.
If the key is not OP_ACCEPT, it must be OP_READ; invoke the data‑reading mechanism described earlier.
(2) Netty workflow steps
Create an EventLoopGroup (NIO thread group) and a ServerBootstrap.
Configure ServerBootstrap with the thread group, SO_BACKLOG option, set NioServerSocketChannel as the channel, and add business handlers.
Bind the port and start the server.
In the business handler (e.g., TimeServerHandler), read client data and respond.
(3) Differences
OP_ACCEPT handling is simplified in Netty because accept logic is uniform across business scenarios.
In NIO you manually allocate a ByteBuffer to read from a Channel; Netty reads directly into a ByteBuf, eliminating the user‑space copy from kernel to user.
Netty provides decoders such as FixedLengthFrameDecoder to handle TCP packet fragmentation, making it convenient to solve sticky‑packet issues.
Thank you for reading, hope it helps :) Source: blog.csdn.net/qq_36520235/article/details/8131818
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.
Java Interview Crash Guide
Dedicated to sharing Java interview Q&A; follow and reply "java" to receive a free premium Java interview guide.
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.
