Skip to content

NIO Deep Dive

java.nio (New I/O, introduced in JDK 1.4) provides a buffer-oriented, non-blocking alternative to The stream-based java.io API. NIO is designed for high-throughput I/O scenarios: network servers Handling thousands of connections, file operations on large files, and memory-mapped I/O.

AspectStream I/O (java.io)NIO (java.nio)
Data modelByte-by-byte or char-by-charBlocks of data (buffers)
DirectionUnidirectional (InputStream/OutputStream)Bidirectional (channels)
BlockingAlways blockingBlocking or non-blocking
File operationsSequentialRandom access, memory-mapped
Threading modelOne thread per connectionOne thread for many connections (selectors)

Buffers are the central data containers in NIO. A buffer is a fixed-capacity, in-memory container For data of a specific primitive type. All buffers extend Buffer.

ByteBuffer is the most commonly used buffer. It holds bytes and provides methods for reading and Writing both primitive types and byte arrays.

// Allocation
ByteBuffer heapBuf = ByteBuffer.allocate(1024); // heap-allocated
ByteBuffer directBuf = ByteBuffer.allocateDirect(1024); // direct (native memory)
// Wrapping an existing array
byte[] data = "Hello, World!".getBytes(StandardCharsets.UTF_8);
ByteBuffer wrapBuf = ByteBuffer.wrap(data);

Every buffer has four core properties:

PropertyDescription
capacityMaximum number of elements the buffer can hold. Set at creation, never changes.
positionIndex of the next element to read or write. Starts at 0, increments on read/write.
limitFirst index that should not be read or written. For write mode, equals capacity. For read mode, equals the number of elements written.
markAn optional bookmark (set via mark()Reset via reset()).

Invariants: 0 <= mark <= position <= limit <= capacity

ByteBuffer buf = ByteBuffer.allocate(256);
// WRITE mode
buf.put("Hello".getBytes(StandardCharsets.UTF_8));
buf.put("World".getBytes(StandardCharsets.UTF_8));
// position = 10, limit = 256
// Flip — prepare for reading (limit = position, position = 0)
buf.flip();
// position = 0, limit = 10
// READ mode
byte[] result = new byte[buf.remaining()];
buf.get(result);
// position = 10, limit = 10
// Clear — prepare for writing again (position = 0, limit = capacity)
buf.clear();
// position = 0, limit = 256
// Compact — copy unread data to the beginning, prepare for writing
// Useful when you"ve partially read and want to append more data
buf.compact();
// Copies bytes from position..limit to 0..remaining, sets position = remaining
// Rewind — position = 0, limit unchanged (re-read the same data)
buf.rewind();
// Heap buffer — backed by a regular Java byte array
ByteBuffer heap = ByteBuffer.allocate(1024);
// Allocated on the Java heap, subject to GC
// Fast allocation, fast access from Java code
// When used for I/O, the data must be copied to/from native memory
// Direct buffer — backed by native memory (malloc)
ByteBuffer direct = ByteBuffer.allocateDirect(1024);
// Allocated outside the Java heap, not subject to GC (except the buffer object itself)
// Slower allocation, but avoids copy when used with I/O operations
// The OS can perform DMA (direct memory access) directly to/from the buffer
### `get` and `put` Operations
ByteBuffer buf = ByteBuffer.allocate(1024);
// Absolute get/put (specify index, does not modify position)
buf.put(5, (byte) 'X');
byte b = buf.get(5);
// Relative get/put (reads/writes at current position, increments position)
buf.put((byte) 'A');
byte c = buf.get();
// Bulk operations
byte[] src = {1, 2, 3, 4, 5};
buf.put(src); // puts entire array
buf.put(src, 1, 3); // puts src[1..3] (bytes 2, 3, 4)
byte[] dst = new byte[5];
buf.get(dst); // reads 5 bytes into dst
// Primitive type access (ByteBuffer-specific)
buf.putInt(42); // 4 bytes, big-endian by default
buf.putLong(123456789L);
int val = buf.getInt();
long lval = buf.getLong();
// Byte order
buf.order(ByteOrder.LITTLE_ENDIAN); // for network protocols, file formats
buf.order(ByteOrder.BIG_ENDIAN); // default, also ByteOrder.nativeOrder()

CharBuffer stores characters. It is often created by wrapping a String or by decoding a ByteBuffer:

// From String
CharBuffer cb = CharBuffer.wrap("Hello, World!");
// Decode from ByteBuffer
CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder();
CharBuffer charBuf = decoder.decode(byteBuffer);
// Encode back to ByteBuffer
CharsetEncoder encoder = StandardCharsets.UTF_8.newEncoder();
ByteBuffer byteBuf = encoder.encode(charBuf);

Channels represent open connections to I/O sources or sinks (files, sockets). Unlike streams, Channels are bidirectional — a FileChannel can both read and write.

FileChannel provides random access to files. It is obtained from FileInputStream FileOutputStream``RandomAccessFileOr FileChannel.open().

// From FileInputStream (read-only)
try (FileInputStream fis = new FileInputStream("data.bin");
FileChannel channel = fis.getChannel()) {
ByteBuffer buf = ByteBuffer.allocate(8192);
while (channel.read(buf) != -1) {
buf.flip();
// process buf
buf.clear();
}
}
// From RandomAccessFile (read-write)
try (RandomAccessFile raf = new RandomAccessFile("data.bin", "rw");
FileChannel channel = raf.getChannel()) {
ByteBuffer buf = ByteBuffer.allocate(1024);
channel.read(buf, 4096); // read from offset 4096
buf.flip();
channel.write(buf, 8192); // write at offset 8192
}
// Modern API (JDK 7+)
try (FileChannel channel = FileChannel.open(Path.of("data.bin"),
StandardOpenOption.READ, StandardOpenOption.WRITE)) {
// read/write operations
}

Zero-copy file operations that transfer data directly between channels without copying through User-space buffers. On supporting OS/filesystems, this is implemented with sendfile or splice System calls.

// Copy a file efficiently (zero-copy when possible)
try (FileChannel src = FileChannel.open(Path.of("source.bin"), StandardOpenOption.READ);
FileChannel dst = FileChannel.open(Path.of("dest.bin"),
StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING)) {
long transferred = src.transferTo(0, src.size(), dst);
System.out.println("Transferred " + transferred + " bytes");
}
### File Locking

FileLock provides advisory locking on files. Advisory means the lock is only enforced if all Processes accessing the file cooperate by acquiring locks. The OS does not prevent a process without A lock from reading or writing.

try (FileChannel channel = FileChannel.open(Path.of("data.lock"),
StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {
// Non-blocking lock attempt
FileLock lock = channel.tryLock();
if (lock == null) {
System.out.println("File is locked by another process");
return;
}
try {
// Critical section — exclusive access to the file
channel.write(ByteBuffer.wrap("important data".getBytes(StandardCharsets.UTF_8)));
} finally {
lock.release();
}
}
// Shared lock (multiple readers, exclusive writers)
FileLock sharedLock = channel.tryLock(0L, Long.MAX_VALUE, true); // true = shared
FileLock exclusiveLock = channel.tryLock(0L, Long.MAX_VALUE, false); // false = exclusive
### Socket Channels
// SocketChannel (client-side TCP)
try (SocketChannel channel = SocketChannel.open()) {
channel.connect(new InetSocketAddress("example.com", 80));
ByteBuffer buf = ByteBuffer.wrap("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
.getBytes(StandardCharsets.UTF_8));
channel.write(buf);
buf.clear();
channel.read(buf);
buf.flip();
System.out.println(StandardCharsets.UTF_8.decode(buf).toString());
}
// ServerSocketChannel (server-side TCP)
try (ServerSocketChannel server = ServerSocketChannel.open()) {
server.bind(new InetSocketAddress(8080));
server.configureBlocking(false); // non-blocking mode
while (true) {
SocketChannel client = server.accept(); // returns null if no connection
if (client != null) {
client.configureBlocking(false);
// handle client with selector
}
}
}
// DatagramChannel (UDP)
try (DatagramChannel channel = DatagramChannel.open()) {
channel.bind(new InetSocketAddress(9000));
ByteBuffer buf = ByteBuffer.allocate(1024);
SocketAddress sender = channel.receive(buf);
buf.flip();
channel.send(buf, sender);
}

A Selector allows a single thread to monitor multiple channels for readiness events (connect, Accept, read, write). This is the foundation of scalable network servers — one thread can handle Thousands of connections.

A SelectionKey represents the registration of a channel with a selector. It contains:

  • The channel being monitored.
  • The selector it is registered with.
  • The set of operations of interest (OP_ACCEPT``OP_CONNECT``OP_READ``OP_WRITE).
  • The set of operations that are ready.
  • An attachment (arbitrary object).
Selector selector = Selector.open();
ServerSocketChannel server = ServerSocketChannel.open();
server.bind(new InetSocketAddress(8080));
server.configureBlocking(false);
server.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
int readyCount = selector.select(); // blocks until at least one channel is ready
if (readyCount == 0) continue;
Set<SelectionKey> readyKeys = selector.selectedKeys();
Iterator<SelectionKey> iter = readyKeys.iterator();
while (iter.hasNext()) {
SelectionKey key = iter.next();
iter.remove(); // MUST remove the key after processing
if (!key.isValid()) continue;
if (key.isAcceptable()) {
acceptConnection(key, selector);
}
if (key.isReadable()) {
readFromChannel(key);
}
if (key.isWritable()) {
writeToChannel(key);
}
}
}
private void acceptConnection(SelectionKey key, Selector selector) throws IOException {
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel client = server.accept();
if (client != null) {
client.configureBlocking(false);
ByteBuffer buffer = ByteBuffer.allocate(4096);
client.register(selector, SelectionKey.OP_READ, buffer);
}
}
private void readFromChannel(SelectionKey key) throws IOException {
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer buffer = (ByteBuffer) key.attachment();
int bytesRead = client.read(buffer);
if (bytesRead == -1) {
key.cancel();
client.close();
return;
}
buffer.flip();
byte[] data = new byte[buffer.remaining()];
buffer.get(data);
buffer.clear();
// Process data, then register for write
String response = processRequest(data);
ByteBuffer writeBuf = ByteBuffer.wrap(response.getBytes(StandardCharsets.UTF_8));
client.register(key.selector(), SelectionKey.OP_WRITE, writeBuf);
}
private void writeToChannel(SelectionKey key) throws IOException {
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer buffer = (ByteBuffer) key.attachment();
client.write(buffer);
if (!buffer.hasRemaining()) {
buffer.clear();
client.register(key.selector(), SelectionKey.OP_READ, buffer);
}
}

selector.wakeup() causes a currently blocked select() call to return immediately. If no select() is in progress, the next select() will return immediately. This is useful for thread Coordination:

// From another thread — wake up the selector loop
selector.wakeup();
// In the selector loop
int readyCount = selector.select();
// If woken up, readyCount may be 0 — check for pending state changes

In non-blocking mode, read() and write() return immediately. read() returns the number of Bytes read (0 if none available, -1 if the channel is closed). write() returns the number of bytes Written (may be less than requested).

channel.configureBlocking(false);
// Non-blocking write — must handle partial writes
ByteBuffer buf = ByteBuffer.wrap(largeData);
while (buf.hasRemaining()) {
int written = channel.write(buf);
if (written == 0) {
// Channel's write buffer is full — register for OP_WRITE
key.interestOps(key.interestOps() | SelectionKey.OP_WRITE);
break;
}
}
## `AsynchronousFileChannel`

Introduced in JDK 7, AsynchronousFileChannel provides asynchronous file I/O operations. It Supports two usage patterns: Future-based and callback-based.

try (AsynchronousFileChannel channel = AsynchronousFileChannel.open(
Path.of("large.dat"),
StandardOpenOption.READ)) {
ByteBuffer buffer = ByteBuffer.allocate(8192);
long position = 0;
Future<Integer> operation = channel.read(buffer, position);
while (!operation.isDone()) {
// Do other work while the read proceeds
doOtherWork();
}
int bytesRead = operation.get(); // blocks until complete (or throws)
buffer.flip();
// process data
}
try (AsynchronousFileChannel channel = AsynchronousFileChannel.open(
Path.of("large.dat"),
StandardOpenOption.WRITE,
StandardOpenOption.CREATE)) {
ByteBuffer buffer = ByteBuffer.wrap("Hello, async world!".getBytes(StandardCharsets.UTF_8));
long position = 0;
channel.write(buffer, position, buffer, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result, ByteBuffer attachment) {
System.out.println("Wrote " + result + " bytes");
// The attachment is the buffer we passed in
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
System.err.println("Write failed: " + exc.getMessage());
}
});
// Continue doing other work — the callback runs on a separate thread
}
## `Path` and `Files` Utility Classes

Path (JDK 7+) replaces File as the primary way to represent file and directory paths. It Supports operations on path components (root, parent, filename) and is immutable.

Path absolute = Path.of("/usr/local/bin/java");
Path relative = Path.of("src/main/resources/config.properties");
// Path operations
Path parent = absolute.getParent(); // /usr/local/bin
Path fileName = absolute.getFileName(); // java
Path root = absolute.getRoot(); // /
int nameCount = absolute.getNameCount(); // 4
// Combining paths
Path combined = Path.of("/home/user").resolve("documents/report.txt");
// /home/user/documents/report.txt
Path normalized = Path.of("/home/user/../admin/./config").normalize();
// /home/admin/config
// Relative path between two paths
Path from = Path.of("/home/user/docs");
Path to = Path.of("/home/user/docs/reports/2024");
Path rel = from.relativize(to);
// reports/2024
// Reading
String content = Files.readString(Path.of("config.txt"));
List<String> lines = Files.readAllLines(Path.of("data.csv"));
byte[] bytes = Files.readAllBytes(Path.of("image.png"));
// Streaming lines (memory-efficient for large files)
try (Stream<String> stream = Files.lines(Path.of("large.log"))) {
stream.filter(line -&gt; line.contains("ERROR"))
.forEach(System.out::println);
}
// Writing
Files.writeString(Path.of("output.txt"), "Hello, World!");
Files.write(Path.of("data.csv"), lines);
Files.write(Path.of("binary.dat"), bytes, StandardOpenOption.CREATE);
// File operations
Files.createFile(Path.of("new.txt"));
Files.createDirectory(Path.of("newdir"));
Files.createDirectories(Path.of("a/b/c/d")); // creates all parent dirs
Files.delete(Path.of("old.txt"));
Files.deleteIfExists(Path.of("maybe.txt"));
Files.move(Path.of("old.txt"), Path.of("new.txt"), StandardCopyOption.REPLACE_EXISTING);
Files.copy(Path.of("source.txt"), Path.of("dest.txt"), StandardCopyOption.REPLACE_EXISTING);
// File attributes
boolean exists = Files.exists(Path.of("config.txt"));
boolean isRegularFile = Files.isRegularFile(Path.of("data.bin"));
boolean isDirectory = Files.isDirectory(Path.of("src"));
long size = Files.size(Path.of("data.bin"));
FileTime lastModified = Files.getLastModifiedTime(Path.of("config.txt"));
// Walking directory trees
try (Stream<Path> walk = Files.walk(Path.of("src/main/java"))) {
walk.filter(Files::isRegularFile)
.filter(p -&gt; p.toString().endsWith(".java"))
.forEach(System.out::println);
}
// Find files
try (Stream<Path> found = Files.find(Path.of("src"), 10,
(path, attrs) -&gt; attrs.isRegularFile() && path.toString().endsWith(".java"))) {
found.forEach(System.out::println);
}

MappedByteBuffer maps a region of a file directly into memory. Reads and writes to the buffer are Reflected in the file. The OS handles paging — only the portions of the file that are actually Accessed are loaded into physical memory.

try (FileChannel channel = FileChannel.open(Path.of("data.bin"),
StandardOpenOption.READ, StandardOpenOption.WRITE)) {
MappedByteBuffer mapped = channel.map(
FileChannel.MapMode.READ_WRITE,
0, // offset
channel.size() // size
);
// Direct memory access — reads/writes go to the file
int value = mapped.getInt(0);
mapped.putInt(0, value + 1);
// Force writes to disk (like fsync)
mapped.force();
}
ModeDescription
READ_ONLYRead-only mapping. Attempts to modify the buffer throw ReadOnlyBufferException.
READ_WRITERead-write mapping. Changes are written back to the file.
PRIVATECopy-on-write. Changes are not written to the file; they are private to this buffer.
  • Structured binary file access — reading/writing fixed-format records at known offsets.
  • Shared memory between processes — two JVM processes can map the same file and communicate through the mapped buffer.
  • Large file processing — process terabyte-scale files without loading them into JVM heap.
## Intuition

Data highways: NIO is like a postal system with express lanes — selectors let one thread manage many channels, making it ideal for high-performance servers.

Why it matters: NIO’s non-blocking nature enables handling thousands of connections with minimal threads, essential for modern network applications.

The key insight: Selectors are the magic — they let one thread monitor multiple channels for readiness, avoiding the overhead of one thread per connection.

ByteBuffer buf = ByteBuffer.allocate(1024);
channel.read(buf);
// BUG — position is at the end, limit is at capacity
byte[] data = new byte[buf.remaining()]; // remaining() returns 0!
buf.get(data); // BufferUnderflowException
// FIX
buf.flip();
byte[] data = new byte[buf.remaining()];
buf.get(data);

Not Handling Partial Writes in Non-Blocking Mode

Section titled “Not Handling Partial Writes in Non-Blocking Mode”
// BUG — assumes write() writes all bytes
channel.write(buffer); // may write only some bytes
// FIX — loop until all bytes are written
while (buffer.hasRemaining()) {
channel.write(buffer);
}
// BUG — selectedKeys() returns keys that were ready at the time of select()
// If you don't remove processed keys, select() returns the same keys again
Set<SelectionKey> keys = selector.selectedKeys();
for (SelectionKey key : keys) {
process(key);
// BUG — key not removed from the set
}
// FIX
Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
while (iter.hasNext()) {
SelectionKey key = iter.next();
iter.remove();
process(key);
}
// The mapped buffer uses native memory that is not freed until the
// MappedByteBuffer object is GC'd. There is no explicit unmap() method
// in the public API (sun.misc.Cleaner exists but is internal).
// Workaround: ensure the buffer becomes unreachable
public void processFile(Path path) throws IOException {
MappedByteBuffer buf;
try (FileChannel fc = FileChannel.open(path, StandardOpenOption.READ)) {
buf = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size());
processData(buf);
}
// buf is now unreachable (assuming processData doesn't store it)
// GC will eventually clean up the native memory mapping
}
// BUG — performing blocking operations in the selector event loop
if (key.isReadable()) {
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer buf = ByteBuffer.allocate(8192);
client.read(buf);
// If you do a blocking database call here, the entire selector
// is blocked — no other connections can be served
String result = blockingDatabaseCall(); // BAD
}
// FIX — offload blocking work to a separate thread pool
if (key.isReadable()) {
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer buf = ByteBuffer.allocate(8192);
client.read(buf);
executor.submit(() -&gt; {
String result = databaseCall();
// Queue response for writing
});
}

Using FileChannel with FileInputStream in Non-Blocking Mode

Section titled “Using FileChannel with FileInputStream in Non-Blocking Mode”
// BUG — FileChannel does not support non-blocking mode
FileChannel fc = new FileInputStream("data.bin").getChannel();
// fc.configureBlocking(false); // throws NonWritableChannelException or no-op
// FileChannel is always blocking. Only SocketChannel and ServerSocketChannel
// support non-blocking mode. Use AsynchronousFileChannel for async file I/O.

Not Flushing MappedByteBuffer Before Closing

Section titled “Not Flushing MappedByteBuffer Before Closing”
// BUG — changes to a MappedByteBuffer may not be written to disk before the
// JVM exits. The OS controls when dirty pages are flushed to disk.
try (FileChannel fc = FileChannel.open(path, READ, WRITE)) {
MappedByteBuffer buf = fc.map(READ_WRITE, 0, 1024);
buf.putInt(0, 42);
// If the JVM crashes here, the write may be lost
} // fc.close() does NOT guarantee flush
// FIX — call force() before closing
try (FileChannel fc = FileChannel.open(path, READ, WRITE)) {
MappedByteBuffer buf = fc.map(READ_WRITE, 0, 1024);
buf.putInt(0, 42);
buf.force(); // forces all dirty pages to disk (like fsync)
}

Allocating direct buffers is expensive (involves a JNI call to allocate native memory). In High-throughput servers, allocate buffers once and reuse them:

public class BufferPool {
private final Queue<ByteBuffer> pool = new ConcurrentLinkedQueue<>();
private final int bufferSize;
public BufferPool(int bufferSize, int initialCapacity) {
this.bufferSize = bufferSize;
for (int i = 0; i &lt; initialCapacity; i++) {
pool.offer(ByteBuffer.allocateDirect(bufferSize));
}
}
public ByteBuffer acquire() {
ByteBuffer buf = pool.poll();
if (buf == null) {
buf = ByteBuffer.allocateDirect(bufferSize);
}
buf.clear();
return buf;
}
public void release(ByteBuffer buf) {
if (buf.capacity() == bufferSize) {
pool.offer(buf);
}
// If buffer size doesn't match, let it be GC'd
}
}

Scatter/gather operations read from a channel into multiple buffers (scatter) or write from multiple Buffers to a channel (gather) in a single system call. This reduces the number of context switches Between user space and kernel space.

// Gather write — write headers and body from separate buffers
ByteBuffer header = ByteBuffer.wrap("HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\n".getBytes(StandardCharsets.UTF_8));
ByteBuffer body = ByteBuffer.wrap("Hello".getBytes(StandardCharsets.UTF_8));
try (FileChannel fc = FileChannel.open(Path.of("response.bin"), WRITE, CREATE)) {
ByteBuffer[] buffers = {header, body};
fc.write(buffers); // single writev system call
}
// Scatter read — read into header and body buffers
ByteBuffer headerBuf = ByteBuffer.allocate(128);
ByteBuffer bodyBuf = ByteBuffer.allocate(1024);
try (FileChannel fc = FileChannel.open(Path.of("request.bin"), READ)) {
ByteBuffer[] buffers = {headerBuf, bodyBuf};
long bytesRead = fc.read(buffers);
}

The classic Reactor pattern with selectors is effective but has limitations:

  • Single selector thread — all I/O events are processed on one thread. CPU-bound processing blocks the selector.
  • Multiple selector threads — can process I/O events in parallel, but requires careful coordination (e.g., wakeup() calls).
  • Selector + worker pool — the selector thread dispatches I/O events; worker threads handle business logic. This is the most common production pattern.
ExecutorService workerPool = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors() * 2);
while (true) {
selector.select();
Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
while (iter.hasNext()) {
SelectionKey key = iter.next();
iter.remove();
if (key.isAcceptable()) {
accept(key, selector);
} else if (key.isReadable()) {
// Dispatch to worker pool instead of processing inline
workerPool.submit(() -> handleRead(key));
} else if (key.isWritable()) {
workerPool.submit(() -> handleWrite(key));
}
}
}

FileChannel vs FileInputStream Performance

Section titled “FileChannel vs FileInputStream Performance”

For sequential file reads, FileInputStream with BufferedInputStream is often faster than FileChannel because the buffer reduces the number of system calls. FileChannel excels at random Access, transferTo/transferFromAnd memory-mapped I/O.

// Sequential read — BufferedInputStream is simpler and often faster
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("large.dat"))) {
byte[] buffer = new byte[8192];
while (bis.read(buffer) != -1) {
// process
}
}
// Random access — FileChannel is the right choice
try (RandomAccessFile raf = new RandomAccessFile("index.dat", "r");
FileChannel fc = raf.getChannel()) {
for (long offset : offsets) {
fc.read(buffer, offset);
// process record at offset
}
}
public class EchoServer {
public static void main(String[] args) throws IOException {
Selector selector = Selector.open();
ServerSocketChannel server = ServerSocketChannel.open();
server.bind(new InetSocketAddress(8080));
server.configureBlocking(false);
server.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("Echo server listening on port 8080");
while (true) {
selector.select();
Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
while (iter.hasNext()) {
SelectionKey key = iter.next();
iter.remove();
if (key.isAcceptable()) {
SocketChannel client = server.accept();
if (client != null) {
client.configureBlocking(false);
client.register(selector, SelectionKey.OP_READ,
ByteBuffer.allocate(256));
}
}
if (key.isReadable()) {
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer buffer = (ByteBuffer) key.attachment();
int bytesRead = client.read(buffer);
if (bytesRead == -1) {
key.cancel();
client.close();
continue;
}
buffer.flip();
client.write(buffer);
buffer.compact();
}
}
}
}
}

Reading a Large File in Chunks with FileChannel

Section titled “Reading a Large File in Chunks with FileChannel”
public static void processLargeFile(Path path) throws IOException {
try (FileChannel channel = FileChannel.open(path, StandardOpenOption.READ)) {
long fileSize = channel.size();
long position = 0;
ByteBuffer buffer = ByteBuffer.allocateDirect(8 * 1024 * 1024); // 8 MB direct buffer
while (position &lt; fileSize) {
buffer.clear();
int bytesRead = channel.read(buffer, position);
if (bytesRead == -1) break;
buffer.flip();
processBuffer(buffer); // application-specific processing
position += bytesRead;
}
}
}
private static void processBuffer(ByteBuffer buffer) {
while (buffer.hasRemaining()) {
// process data
byte b = buffer.get();
}
}

This topic covers the core concepts of nio deep dive, including underlying theory, practical implementation, and key applications.

Key concepts include:

  • core concepts and terminology
  • algorithms and computational thinking
  • practical implementation
  • security and ethical considerations
  • applications in the real world

Understanding these concepts thoroughly is essential for both examinations and practical programming, and requires both theoretical knowledge and hands-on practice.

Worked examples demonstrating the application of key concepts are covered in the detailed sub-pages linked above.