1. The Hook (The "Byte-Sized" Intro)
- In a Nutshell: NIO (New I/O, Java 1.4) offers non-blocking I/O via Buffers and Channels. Buffer = container for data (ByteBuffer, CharBuffer). Channel = connection to I/O source (FileChannel, SocketChannel).
- Key operations: put() (write to buffer), get() (read from buffer), flip() (switch read/write mode), clear() (reset). FileChannel enables memory-mapped files, file locking. Faster than traditional I/O for large files! Use NIO.2 (java.nio.file) for modern file operations.
Think of shipping. Traditional I/O = conveyor belt (continuous stream). NIO = shipping containers (buffers) loaded onto trucks (channels). You fill container, send it, empty it, reuse it!
2. Conceptual Clarity (The "Simple" Tier)
💡 The Analogy
- Buffer: Shopping cart (collect items, then checkout at once)
- Channel: Highway (two-way road for data transfer)
- flip(): Turn shopping cart around (from filling mode to emptying mode)
Buffer States
stateDiagram-v2
[*] --> Writing: Initial state
Writing --> Reading: flip()
Reading --> Writing: clear()
Reading --> Writing: compact()
3. Technical Mastery (The "Deep Dive")
Buffer Core Concepts
- Capacity: Total size (fixed at creation)
- Position: Current read/write position
- Limit: Maximum readable/writable position
- Mark: Saved position (for reset)
text
Initial: position=0, limit=capacity
After put: position++
After flip: limit=position, position=0 (ready to read)
After get: position++
After clear: position=0, limit=capacity (ready to write)4. Interactive & Applied Code
java
import java.nio.*;
import java.nio.channels.*;
import java.nio.file.*;
import java.io.*;
public class NIOBasicsDemo {
public static void main(String[] args) throws Exception {
demonstrateByteBuffer();
demonstrateFileChannel();
demonstrateMemoryMappedFile();
}
// ByteBuffer operations
static void demonstrateByteBuffer() {
System.out.println("=== BYTE BUFFER ===");
// Allocate buffer (capacity = 10)
ByteBuffer buffer = ByteBuffer.allocate(10);
printBufferState("Initial", buffer);
// Write to buffer
buffer.put((byte) 'H');
buffer.put((byte) 'e');
buffer.put((byte) 'l');
buffer.put((byte) 'l');
buffer.put((byte) 'o');
printBufferState("After put", buffer);
// Flip for reading (limit=position, position=0)
buffer.flip();
printBufferState("After flip", buffer);
// Read from buffer
while (buffer.hasRemaining()) {
System.out.print((char) buffer.get());
}
System.out.println();
printBufferState("After get", buffer);
// Clear for reuse
buffer.clear();
printBufferState("After clear", buffer);
}
static void printBufferState(String label, ByteBuffer buffer) {
System.out.printf("%s: position=%d, limit=%d, capacity=%d%n",
label, buffer.position(), buffer.limit(), buffer.capacity());
}
// FileChannel (NIO file I/O)
static void demonstrateFileChannel() throws Exception {
System.out.println("\n=== FILE CHANNEL ===");
Path source = Paths.get("source.txt");
Path dest = Paths.get("dest.txt");
// Create source file
Files.writeString(source, "Hello, FileChannel!");
// Copy using FileChannel
try (FileChannel sourceChannel = FileChannel.open(source,
StandardOpenOption.READ);
FileChannel destChannel = FileChannel.open(dest,
StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {
// Method 1: Transfer directly
sourceChannel.transferTo(0, sourceChannel.size(), destChannel);
System.out.println("File copied using transferTo()");
// Method 2: Using buffer
ByteBuffer buffer = ByteBuffer.allocate(1024);
sourceChannel.position(0); // Reset
while (sourceChannel.read(buffer) > 0) {
buffer.flip();
destChannel.write(buffer);
buffer.clear();
}
}
}
// Memory-mapped file (extremely fast for large files)
static void demonstrateMemoryMappedFile() throws Exception {
System.out.println("\n=== MEMORY-MAPPED FILE ===");
Path file = Paths.get("mapped.dat");
// Create file with FileChannel
try (FileChannel channel = FileChannel.open(file,
StandardOpenOption.CREATE, StandardOpenOption.READ,
StandardOpenOption.WRITE)) {
// Map file to memory (virtual memory)
MappedByteBuffer buffer = channel.map(
FileChannel.MapMode.READ_WRITE, 0, 1024);
// Write to memory-mapped buffer (writes to file!)
buffer.put("Memory-mapped I/O is fast!".getBytes());
// Read from memory-mapped buffer
buffer.flip();
byte[] data = new byte[buffer.limit()];
buffer.get(data);
System.out.println("Read: " + new String(data));
}
System.out.println("Memory-mapped files ideal for:");
System.out.println(" - Large file processing");
System.out.println(" - Inter-process communication");
System.out.println(" - Database implementations");
}
}5. The Comparison & Decision Layer
| Feature | Traditional I/O | NIO |
|---|---|---|
| Model | Stream-based | Buffer/Channel-based |
| Blocking | Yes | Optional (non-blocking) |
| Performance | Slower | Faster (large files) |
| Complexity | Simple | More complex |
| Use for | Small files, simple tasks | Large files, high performance |
6. The "Interview Corner" (The Edge)
The "Killer" Interview Question: "Why do you need to call flip() between writing and reading?" Answer: flip() sets limit=position, position=0 for reading what you wrote!
java
ByteBuffer buffer = ByteBuffer.allocate(10);
// Write 5 bytes
buffer.put(...); // position=0→5, limit=10
// WITHOUT flip: Try to read
buffer.get(); // Reads from position 5 (nothing written there!)
// WITH flip: Prepare for reading
buffer.flip(); // limit=5, position=0
buffer.get(); // ✅ Reads from position 0 (data you wrote!)Pro-Tip: Direct vs Heap ByteBuffer:
java
// Heap buffer (JVM heap, slower but GC-managed)
ByteBuffer heap = ByteBuffer.allocate(1024);
// Direct buffer (native memory, faster I/O but manual management)
ByteBuffer direct = ByteBuffer.allocateDirect(1024);Use direct buffers for high-performance I/O (network, large files).
Use heap buffers for general-purpose needs (easier, GC-managed).