Giới thiệu I/O trong Java
Sau bài này, bạn sẽ:
- Hiểu I/O (Input/Output) trong Java và tại sao cần xử lý dữ liệu từ/đến external sources
- Phân biệt Stream-based I/O (java.io) vs NIO (java.nio) - blocking vs non-blocking
- Nắm được các khái niệm: Streams, Readers/Writers, Channels, Buffers, Selectors
- So sánh I/O vs NIO: byte streams vs character streams, synchronous vs asynchronous
- Biết khi nào dùng I/O và khi nào dùng NIO dựa trên use case
I/O là gì?
I/O (Input/Output) là quá trình trao đổi dữ liệu giữa chương trình Java và các nguồn bên ngoài như:
- File trên ổ đĩa
- Network sockets (kết nối mạng)
- Console (bàn phím, màn hình)
- Database (thông qua JDBC)
- Memory buffers (bộ nhớ đệm)
Java cung cấp hai bộ API chính để xử lý I/O:
- java.io - API truyền thống (từ Java 1.0)
- java.nio - New I/O (từ Java 1.4) và NIO.2 (từ Java 7)
Không có API nào "tốt hơn" - chúng phục vụ các use case khác nhau. Stream-based I/O đơn giản hơn cho hầu hết tác vụ thông thường, trong khi NIO mạnh mẽ hơn cho high-performance applications.
Java I/O Overview: java.io vs java.nio
java.io - Stream-based I/O
┌─────────────────────────────────────┐
│ java.io Package │
├─────────────────────────────────────┤
│ • Stream-oriented (dòng dữ liệu) │
│ • Blocking I/O (đồng bộ) │
│ • Character và Byte streams │
│ • File, Reader, Writer classes │
└─────────────────────────────────────┘
Đặc điểm:
- Dữ liệu được đọc/ghi theo dòng tuần tự (stream)
- Blocking: thread bị chặn cho đến khi I/O hoàn thành
- API đơn giản, dễ hiểu và sử dụng
- Phù hợp cho hầu hết ứng dụng thông thường
java.nio - Channel-based I/O
┌─────────────────────────────────────┐
│ java.nio Package │
├─────────────────────────────────────┤
│ • Buffer-oriented (bộ đệm) │
│ • Non-blocking I/O (có thể bất đồng bộ)│
│ • Channels và Buffers │
│ • Selectors cho I/O multiplexing │
└─────────────────────────────────────┘
Đặc điểm:
- Dữ liệu được đọc vào Buffer, có thể di chuyển qua lại
- Non-blocking: thread có thể làm việc khác trong khi chờ I/O
- Selectors: một thread có thể quản lý nhiều kênh I/O
- Hiệu suất cao hơn cho ứng dụng cần xử lý nhiều kết nối đồng thời
Stream-based I/O vs Channel-based I/O
| Tiêu chí | java.io (Streams) | java.nio (Channels) |
|---|---|---|
| Hướng xử lý | Stream-oriented (dòng) | Buffer-oriented (bộ đệm) |
| Cơ chế | Blocking (đồng bộ) | Non-blocking (có thể bất đồng bộ) |
| Đọc dữ liệu | Tuần tự, không quay lại | Có thể di chuyển trong buffer |
| API | Đơn giản, trực quan | Phức tạp hơn, linh hoạt hơn |
| Performance | Tốt cho I/O đơn giản | Tốt hơn cho high-throughput |
| Use case | File I/O thông thường | Server xử lý nhiều connections |
Ví dụ so sánh code
java.io - Stream-based:
import java.io.*;
public class StreamExample {
public static void main(String[] args) throws IOException {
// Đọc file theo stream - blocking
try (BufferedReader reader = new BufferedReader(
new FileReader("data.txt"))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line); // Đọc tuần tự, không quay lại
}
}
}
}
java.nio - Channel-based:
import java.nio.*;
import java.nio.channels.*;
import java.nio.file.*;
public class ChannelExample {
public static void main(String[] args) throws IOException {
// Đọc file qua channel với buffer
try (FileChannel channel = FileChannel.open(
Paths.get("data.txt"), StandardOpenOption.READ)) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (channel.read(buffer) > 0) {
buffer.flip(); // Chuyển sang chế độ đọc
while (buffer.hasRemaining()) {
System.out.print((char) buffer.get());
}
buffer.clear(); // Chuẩn bị cho lần đọc tiếp theo
}
}
}
}
Byte Streams vs Character Streams
Java phân biệt rõ giữa byte streams (xử lý dữ liệu nhị phân) và character streams (xử lý văn bản).
Byte Streams
InputStream (abstract) OutputStream (abstract)
├── FileInputStream ├── FileOutputStream
├── ByteArrayInputStream ├── ByteArrayOutputStream
├── BufferedInputStream ├── BufferedOutputStream
└── ObjectInputStream └── ObjectOutputStream
Sử dụng khi:
- Đọc/ghi file nhị phân (ảnh, video, audio, binary data)
- Xử lý dữ liệu ở mức byte thô
Ví dụ:
// Copy file nhị phân (ảnh, video, etc.)
try (FileInputStream in = new FileInputStream("image.jpg");
FileOutputStream out = new FileOutputStream("copy.jpg")) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
}
Character Streams
Reader (abstract) Writer (abstract)
├── FileReader ├── FileWriter
├── BufferedReader ├── BufferedWriter
├── InputStreamReader ├── OutputStreamWriter
└── StringReader └── PrintWriter
Sử dụng khi:
- Đọc/ghi file văn bản (text files)
- Xử lý Unicode characters
- Cần encoding/decoding (UTF-8, ISO-8859-1, etc.)
Ví dụ:
// Đọc file văn bản với encoding UTF-8
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(
new FileInputStream("vietnamese.txt"),
StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line); // Đọc từng dòng
}
}
Khi làm việc với văn bản tiếng Việt, luôn luôn chỉ định encoding UTF-8 một cách tường minh. Nếu không, Java sẽ dùng default encoding của hệ thống và có thể gây lỗi font chữ.
I/O Class Hierarchy
┌────────────────────────────────────────────────────┐
│ java.io Package │
└────────────────────────────────────────────────────┘
│
┌───────────────┴──────────────┐
│ │
Byte Streams Character Streams
│ │
┌───────┴────────┐ ┌─────────┴──────────┐
│ │ │ │
InputStream OutputStream Reader Writer
│ │ │ │
├─FileInputStream│ ├─FileReader ├─FileWriter
├─BufferedInput~ │ ├─BufferedReader ├─BufferedWriter
├─ByteArrayInput~│ ├─InputStreamReader ├─OutputStreamWriter
└─ObjectInput~ │ └─StringReader └─PrintWriter
│
├─FileOutputStream
├─BufferedOutput~
├─ByteArrayOutput~
└─ObjectOutput~
┌────────────────────────────────────────────────────┐
│ java.nio Package (NIO) │
└────────────────────────────────────────────────────┘
│
┌───────────────┴──────────────┐
│ │
Channels Buffers
│ │
├─FileChannel ├─ByteBuffer
├─SocketChannel ├─CharBuffer
├─ServerSocketChannel ├─IntBuffer
└─DatagramChannel └─...
Blocking I/O vs Non-blocking I/O
Blocking I/O (java.io)
// Thread bị CHẶN cho đến khi đọc xong
InputStream in = socket.getInputStream();
int data = in.read(); // Thread dừng lại ở đây cho đến khi có dữ liệu
System.out.println("Dữ liệu: " + data);
Đặc điểm:
- Thread bị block (tạm dừng) cho đến khi I/O hoàn thành
- Đơn giản, dễ hiểu, code tuần tự
- Mỗi connection cần một thread riêng
- Vấn đề: Với 10,000 connections → cần 10,000 threads (resource intensive!)
Non-blocking I/O (java.nio)
// Thread KHÔNG bị chặn - có thể làm việc khác
SocketChannel channel = SocketChannel.open();
channel.configureBlocking(false); // Non-blocking mode
ByteBuffer buffer = ByteBuffer.allocate(1024);
int bytesRead = channel.read(buffer);
if (bytesRead == -1) {
// Connection closed
} else if (bytesRead == 0) {
// Chưa có dữ liệu - thread có thể làm việc khác
} else {
// Có dữ liệu - xử lý
}
Đặc điểm:
- Thread không bị block - kiểm tra xem I/O có sẵn hay không
- Một thread có thể quản lý nhiều channels (via Selector)
- Phức tạp hơn, cần hiểu rõ Buffer và Selector
- Lợi ích: 1 thread có thể xử lý hàng ngàn connections
Selector - I/O Multiplexing
// MỘT thread xử lý NHIỀU channels
Selector selector = Selector.open();
channel1.register(selector, SelectionKey.OP_READ);
channel2.register(selector, SelectionKey.OP_READ);
channel3.register(selector, SelectionKey.OP_READ);
// ... có thể register hàng ngàn channels
while (true) {
selector.select(); // Chờ đến khi ít nhất 1 channel ready
Set<SelectionKey> selectedKeys = selector.selectedKeys();
for (SelectionKey key : selectedKeys) {
if (key.isReadable()) {
// Xử lý channel này
SocketChannel channel = (SocketChannel) key.channel();
// ... đọc dữ liệu
}
}
selectedKeys.clear();
}
Một server chat với 10,000 users online:
- Blocking I/O: Cần 10,000 threads → tốn bộ nhớ, context switching chậm
- Non-blocking I/O với Selector: 1-10 threads xử lý được → hiệu quả hơn nhiều
Stream vs Channel - Hiểu bản chất khác biệt
Stream: Ống nước một chiều
Tưởng tượng stream như ống nước một chiều - nước chỉ chảy theo một hướng duy nhất:
Stream (Ống nước một chiều):
InputStream: File ──[chỉ đọc]──> Application
↑
Một chiều
OutputStream: Application ──[chỉ ghi]──> File
↑
Một chiều
- InputStream: Chỉ đọc, không ghi
- OutputStream: Chỉ ghi, không đọc
- Nếu cần cả đọc VÀ ghi → phải mở 2 streams riêng biệt!
Ví dụ:
// Stream: Cần 2 objects riêng cho đọc và ghi
FileInputStream in = new FileInputStream("data.txt"); // Chỉ đọc
FileOutputStream out = new FileOutputStream("data.txt"); // Chỉ ghi
// Output: Lỗi! Không thể vừa đọc vừa ghi cùng lúc với streams
Channel: Đường hai chiều
Tưởng tượng channel như đường hai chiều - xe có thể chạy cả 2 hướng:
Channel (Đường hai chiều):
FileChannel: File ⟺ [đọc VÀ ghi] ⟺ Application
↑
Hai chiều
- FileChannel: Có thể đọc VÀ ghi cùng lúc
- Chỉ cần 1 channel object
- Linh hoạt hơn, hiệu quả hơn cho random access
Ví dụ:
// Channel: 1 object cho cả đọc và ghi
try (RandomAccessFile file = new RandomAccessFile("data.txt", "rw");
FileChannel channel = file.getChannel()) {
ByteBuffer buffer = ByteBuffer.allocate(100);
// Đọc từ file
channel.read(buffer);
// Di chuyển đến vị trí khác
channel.position(50);
// Ghi vào file
buffer.flip();
channel.write(buffer);
// Có thể đọc/ghi bất kỳ lúc nào!
}
- Stream = ống nước một chiều (water pipe) → chỉ IN hoặc OUT
- Channel = đường hai chiều (highway) → cả READ và WRITE
Blocking vs Non-blocking - Deep Dive
Blocking I/O: Thread đợi cho đến khi xong
Blocking I/O giống như bạn đứng chờ ở máy ATM - không thể làm gì khác cho đến khi rút tiền xong:
Thread lifecycle với Blocking I/O:
Thread: [Running] ─read()─> [BLOCKED ❌] ─data arrives─> [Running]
↑
Đứng yên chờ!
Không làm gì được!
Ví dụ:
// Blocking I/O
Socket socket = new Socket("server.com", 8080);
InputStream in = socket.getInputStream();
// Thread BỊ CHẶN ở đây cho đến khi có data!
int data = in.read(); // ← Thread dừng lại hoàn toàn
System.out.println("Data: " + data); // Chỉ chạy khi có data
Vấn đề với 10,000 connections:
10,000 clients = 10,000 threads = 10,000 threads bị BLOCKED
→ Tốn bộ nhớ (mỗi thread ~ 1MB stack)
→ Context switching chậm
→ Server không scale!
Non-blocking I/O: Thread kiểm tra rồi làm việc khác
Non-blocking I/O giống như bạn đặt hàng online - vẫn làm việc khác trong khi chờ:
Thread lifecycle với Non-blocking I/O:
Thread: [Running] ─read()─> [Kiểm tra] ─no data?─> [Running] ─làm việc khác─>
↓ has data?
[Process data]
Ví dụ:
// Non-blocking I/O
SocketChannel channel = SocketChannel.open();
channel.connect(new InetSocketAddress("server.com", 8080));
channel.configureBlocking(false); // ← Non-blocking mode
ByteBuffer buffer = ByteBuffer.allocate(1024);
int bytesRead = channel.read(buffer);
if (bytesRead == -1) {
// Connection đóng
System.out.println("Connection closed");
} else if (bytesRead == 0) {
// Chưa có data - thread có thể làm việc khác!
System.out.println("No data yet, doing other work...");
doOtherWork(); // ← Thread không bị block!
} else {
// Có data - xử lý
System.out.println("Got " + bytesRead + " bytes");
processData(buffer);
}
Lợi ích với 10,000 connections:
10,000 clients + Non-blocking I/O + Selector = chỉ cần vài threads!
→ Tiết kiệm bộ nhớ
→ Không context switching nhiều
→ Server scale tốt!
// SAI: Quên set non-blocking mode
SocketChannel channel = SocketChannel.open();
// Mặc định là BLOCKING!
int bytesRead = channel.read(buffer); // Vẫn block!
// ĐÚNG: Phải set non-blocking
channel.configureBlocking(false);
int bytesRead = channel.read(buffer); // Non-blocking!
// Output: bytesRead có thể = 0 (chưa có data)
NIO Selector - Một nhân viên phục vụ nhiều khách
Khái niệm Selector
Selector giống như 1 nhân viên lễ tân phục vụ nhiều khách hàng trong khách sạn:
Traditional I/O (1 thread per connection):
┌─────────┐ ┌─────────┐ ┌─────────┐
│Client 1 │◄────►│Thread 1 │ │ │
└─────────┘ └─────────┘ │ │
┌─────────┐ ┌─────────┐ │ Server │
│Client 2 │◄────►│Thread 2 │ │ │
└─────────┘ └─────────┘ │ │
┌─────────┐ ┌─────────┐ │ │
│Client 3 │◄────►│Thread 3 │ │ │
└─────────┘ └─────────┘ └─────────┘
↑
Mỗi client = 1 thread!
NIO Selector (1 thread, many connections):
┌─────────┐
│Client 1 │◄─┐
└─────────┘ │
┌─────────┐ │ ┌──────────┐ ┌─────────┐
│Client 2 │◄─┼──►│ Selector │◄───►│ Thread │
└─────────┘ │ └──────────┘ └─────────┘
┌─────────┐ │ ↑
│Client 3 │◄─┘ "Ai cần phục vụ?"
└─────────┘
↑
1 thread phục vụ nhiều clients!
Cách hoạt động:
- Register các channels với Selector
- Selector theo dõi tất cả channels
- Khi channel nào ready (có data, có thể ghi, etc.) → Selector thông báo
- Thread xử lý channel đó
- Quay lại bước 2
Code example: Selector với nhiều channels
import java.nio.channels.*;
import java.nio.ByteBuffer;
import java.net.InetSocketAddress;
import java.util.Set;
import java.util.Iterator;
public class SelectorExample {
public static void main(String[] args) throws Exception {
// Tạo Selector
Selector selector = Selector.open();
// Tạo ServerSocketChannel
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.bind(new InetSocketAddress(8080));
serverChannel.configureBlocking(false);
// Register với Selector: quan tâm ACCEPT events
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("Server started on port 8080...");
// Main loop: 1 thread xử lý NHIỀU connections!
while (true) {
// Chờ cho đến khi ít nhất 1 channel ready
selector.select(); // Blocking call, nhưng chờ NHIỀU channels!
// Lấy tất cả channels ready
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectedKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
if (key.isAcceptable()) {
// Client mới connect
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel client = server.accept();
client.configureBlocking(false);
// Register client channel: quan tâm READ events
client.register(selector, SelectionKey.OP_READ);
System.out.println("New client connected: " + client);
} else if (key.isReadable()) {
// Client gửi data
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(256);
int bytesRead = client.read(buffer);
if (bytesRead == -1) {
// Client disconnect
client.close();
System.out.println("Client disconnected");
} else {
buffer.flip();
System.out.println("Received: " + new String(buffer.array(), 0, bytesRead));
// Echo back to client
buffer.rewind();
client.write(buffer);
}
}
}
}
}
}
"A selector is a Java NIO component which can examine one or more NIO Channel instances, and determine which channels are ready for reading or writing. This way a single thread can manage multiple channels, and thus multiple network connections."
Lợi ích của Selector
| Truyền thống (1 thread/connection) | Selector (1 thread/nhiều connections) |
|---|---|
| 10,000 connections = 10,000 threads | 10,000 connections = 1-10 threads |
| Memory: ~10GB (10K × 1MB/thread) | Memory: ~10MB |
| Context switching: nhiều | Context switching: ít |
| Performance: kém | Performance: tốt |
Asynchronous I/O (NIO.2) - Callback-based
Asynchronous I/O (Java 7+) là phiên bản nâng cấp của Non-blocking I/O, sử dụng callbacks.
AsynchronousFileChannel - Future-based
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.file.*;
import java.util.concurrent.Future;
public class AsyncFileRead {
public static void main(String[] args) throws Exception {
Path path = Paths.get("large.txt");
try (AsynchronousFileChannel channel = AsynchronousFileChannel.open(
path, StandardOpenOption.READ)) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
// Đọc async, trả về Future
Future<Integer> result = channel.read(buffer, 0);
// Làm việc khác trong khi đọc!
System.out.println("Reading file asynchronously...");
doOtherWork();
// Lấy kết quả (block nếu chưa xong)
Integer bytesRead = result.get();
System.out.println("Read " + bytesRead + " bytes");
buffer.flip();
System.out.println("Content: " + new String(buffer.array(), 0, bytesRead));
}
}
static void doOtherWork() {
System.out.println("Doing other work while I/O is in progress...");
}
}
AsynchronousFileChannel - Callback-based
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.file.*;
public class AsyncFileWithCallback {
public static void main(String[] args) throws Exception {
Path path = Paths.get("data.txt");
try (AsynchronousFileChannel channel = AsynchronousFileChannel.open(
path, StandardOpenOption.READ)) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
// Đọc async với callback
channel.read(buffer, 0, buffer, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer bytesRead, ByteBuffer attachment) {
System.out.println("Read completed: " + bytesRead + " bytes");
attachment.flip();
System.out.println("Content: " + new String(attachment.array(), 0, bytesRead));
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
System.err.println("Read failed: " + exc.getMessage());
}
});
// Main thread không bị block!
System.out.println("Main thread continues...");
Thread.sleep(1000); // Chờ callback hoàn thành
}
}
}
- Synchronous: Gọi hàm → đợi kết quả → tiếp tục (blocking)
- Asynchronous: Gọi hàm → tiếp tục ngay → callback khi xong (non-blocking)
When to Use IO vs NIO - Decision Matrix
Decision Matrix
| Tiêu chí | java.io | java.nio |
|---|---|---|
| Số lượng connections | < 1,000 | > 1,000 |
| Kích thước data mỗi connection | Lớn (MB-GB) | Nhỏ (KB) |
| Pattern | Sequential I/O | Random access |
| Complexity tolerance | Đơn giản | Phức tạp OK |
| Latency requirement | Normal | Low latency |
| Throughput requirement | Normal | High throughput |
Ví dụ cụ thể:
✅ Dùng java.io khi:
- Đọc file log 1GB tuần tự
- Copy file backup
- Đọc config file nhỏ
- Web app với < 100 concurrent users
✅ Dùng java.nio khi:
- Chat server với 10,000 users online
- Game server với nhiều connections
- File server với random access
- High-frequency trading system
Architecture Diagrams: IO vs NIO Models
Traditional I/O Model (1 thread per connection)
NIO Model with Selector (Few threads, many connections)
Blocking vs Non-blocking Flow
IO vs NIO Decision Matrix
Decision Flowchart
Asynchronous vs Non-blocking
Khi nào dùng I/O vs NIO?
Sử dụng java.io (Stream-based) khi:
✅ Đọc/ghi file đơn giản
// Đọc config file
Properties props = new Properties();
try (FileInputStream in = new FileInputStream("config.properties")) {
props.load(in);
}
✅ Xử lý văn bản, logging
// Ghi log file
try (PrintWriter writer = new PrintWriter(new FileWriter("app.log", true))) {
writer.println("[INFO] Application started");
}
✅ Code đơn giản, dễ maintain
- Đội ngũ developer ít kinh nghiệm
- Ứng dụng nhỏ, không cần high performance
✅ I/O không phải bottleneck
- Desktop applications
- Batch processing scripts
Sử dụng java.nio (Channel-based) khi:
✅ High-performance server
// Chat server, game server - nhiều connections đồng thời
Selector selector = Selector.open();
// ... xử lý hàng ngàn clients với vài threads
✅ Large file processing
// Memory-mapped file - đọc file lớn hiệu quả
MappedByteBuffer buffer = FileChannel.open(path)
.map(FileChannel.MapMode.READ_ONLY, 0, fileSize);
✅ Cần non-blocking I/O
- Asynchronous I/O operations
- Event-driven architecture
✅ File system operations phức tạp
// NIO.2: Watch service, directory traversal, atomic operations
WatchService watchService = FileSystems.getDefault().newWatchService();
Bảng so sánh tổng hợp
| Use Case | Nên dùng | Lý do |
|---|---|---|
| Đọc config file | java.io | Đơn giản, dễ hiểu |
| Web server (Tomcat, Netty) | java.nio | Xử lý nhiều connections |
| Copy file | java.nio (NIO.2) | Files.copy() tiện hơn |
| Đọc file text nhỏ | java.io | Code ngắn gọn |
| Đọc file > 2GB | java.nio | Memory-mapped files |
| Chat client-server | java.nio | Non-blocking I/O |
| Logging framework | java.io | Stream-based đủ dùng |
| File watching | java.nio | WatchService API |
Nhiều developer mới nghĩ "NIO nhanh hơn nên luôn dùng NIO" - SAI! NIO phức tạp hơn và không phải lúc nào cũng nhanh hơn. Đọc 1 file nhỏ với BufferedReader thường đơn giản và đủ nhanh hơn so với việc setup Channel + Buffer.
Tổng kết
Điều cần nhớ
- java.io: Stream-based, blocking, đơn giản → dùng cho hầu hết I/O thông thường
- java.nio: Buffer-based, non-blocking, phức tạp → dùng cho high-performance
- Byte streams: Xử lý binary data (ảnh, video, etc.)
- Character streams: Xử lý text với encoding (luôn dùng UTF-8 cho tiếng Việt!)
- Blocking I/O: Thread đợi I/O → đơn giản nhưng không scale
- Non-blocking I/O: Thread không đợi → phức tạp nhưng scale tốt
Tiếp theo
Trong các bài tiếp theo, chúng ta sẽ học:
- File I/O cơ bản: File class, FileReader/Writer, BufferedReader/Writer
- Streams, Readers, Writers: InputStream/OutputStream hierarchy, decorator pattern
- NIO Channels & Buffers: ByteBuffer, FileChannel, memory-mapped files
- NIO.2 Path & Files: Modern file API (Java 7+)
- Serialization: Lưu objects vào file
Bài tập thực hành:
- Viết chương trình đọc file text và đếm số dòng, số từ
- So sánh performance: copy file 100MB bằng FileInputStream vs FileChannel
- Nghiên cứu source code của một web server (Tomcat/Netty) xem họ dùng NIO như thế nào