Java I/O và NIO
1. Java I/O (Input/Output)
Khái Niệm Cơ Bản
- I/O là gì?
- Input/Output: Quá trình đọc/ghi dữ liệu
- Tương tác với file, network, console,...
-
Cơ sở cho việc xử lý dữ liệu trong Java
-
Phân loại I/O trong Java:
- Byte-oriented (Stream): Xử lý dữ liệu nhị phân
- Character-oriented (Reader/Writer): Xử lý văn bản
- Buffered I/O: Sử dụng buffer để tối ưu hiệu năng
- Object I/O: Serialization/Deserialization objects
1.1 Stream-based I/O
- Đặc điểm:
- Xử lý dữ liệu ở mức byte
- Phù hợp cho dữ liệu nhị phân
- Hiệu quả cho file lớn
public class IOStreamExample {
// Đọc file sử dụng FileInputStream
public void readFile(String path) {
try (FileInputStream fis = new FileInputStream(path)) {
int data;
while ((data = fis.read()) != -1) {
System.out.print((char) data);
}
} catch (IOException e) {
e.printStackTrace();
}
}
// Ghi file sử dụng FileOutputStream
public void writeFile(String path, String content) {
try (FileOutputStream fos = new FileOutputStream(path)) {
byte[] bytes = content.getBytes();
fos.write(bytes);
} catch (IOException e) {
e.printStackTrace();
}
}
}
1.2 Character-based I/O
- Đặc điểm:
- Xử lý dữ liệu ở mức ký tự
- Hỗ trợ encoding/decoding
- Phù hợp cho xử lý văn bản
public class CharacterIOExample {
// Đọc file sử dụng FileReader
public void readFile(String path) {
try (FileReader reader = new FileReader(path)) {
int character;
while ((character = reader.read()) != -1) {
System.out.print((char) character);
}
} catch (IOException e) {
e.printStackTrace();
}
}
// Ghi file sử dụng FileWriter
public void writeFile(String path, String content) {
try (FileWriter writer = new FileWriter(path)) {
writer.write(content);
} catch (IOException e) {
e.printStackTrace();
}
}
}
1.3 Buffered I/O
- Đặc điểm:
- Sử dụng buffer để giảm I/O operations
- Cải thiện hiệu năng đáng kể
- Hỗ trợ đọc theo dòng
public class BufferedIOExample {
// Đọc file sử dụng BufferedReader
public void readFile(String path) {
try (BufferedReader reader = new BufferedReader(
new FileReader(path))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
// Ghi file sử dụng BufferedWriter
public void writeFile(String path, List<String> lines) {
try (BufferedWriter writer = new BufferedWriter(
new FileWriter(path))) {
for (String line : lines) {
writer.write(line);
writer.newLine();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
2. Java NIO (New I/O)
Khái Niệm
- NIO là gì?
- Non-blocking I/O: Hỗ trợ xử lý không đồng bộ
- Channel và Buffer: Mô hình I/O mới
-
Selector: Quản lý nhiều kết nối đồng thời
-
So sánh với I/O truyền thống:
- I/O: Stream-oriented, blocking
- NIO: Buffer-oriented, non-blocking
- NIO: Hiệu quả hơn cho nhiều connections
2.1 Channel và Buffer
- Channel:
- Kênh truyền dữ liệu hai chiều
- Hỗ trợ đọc/ghi không đồng bộ
-
Nhiều loại channel khác nhau
-
Buffer:
- Container cho dữ liệu
- Hỗ trợ đọc/ghi trực tiếp
- Có các mode: read/write
public class ChannelExample {
// Đọc file sử dụng FileChannel
public void readFile(String path) {
try (FileChannel channel = FileChannel.open(
Paths.get(path), StandardOpenOption.READ)) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (channel.read(buffer) != -1) {
buffer.flip();
while (buffer.hasRemaining()) {
System.out.print((char) buffer.get());
}
buffer.clear();
}
} catch (IOException e) {
e.printStackTrace();
}
}
// Ghi file sử dụng FileChannel
public void writeFile(String path, String content) {
try (FileChannel channel = FileChannel.open(
Paths.get(path),
StandardOpenOption.CREATE,
StandardOpenOption.WRITE)) {
ByteBuffer buffer = ByteBuffer.wrap(
content.getBytes());
channel.write(buffer);
} catch (IOException e) {
e.printStackTrace();
}
}
}
2.2 Selector
- Đặc điểm:
- Quản lý nhiều channels
- Hỗ trợ non-blocking I/O
- Hiệu quả cho server applications
public class SelectorExample {
public void demonstrateSelector() throws IOException {
// Tạo selector
Selector selector = Selector.open();
// Tạo server socket channel
ServerSocketChannel serverSocket = ServerSocketChannel.open();
serverSocket.bind(new InetSocketAddress("localhost", 5000));
serverSocket.configureBlocking(false);
// Đăng ký channel với selector
serverSocket.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
int readyChannels = selector.select();
if (readyChannels == 0) continue;
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if (key.isAcceptable()) {
// Xử lý accept
handleAccept(serverSocket, selector);
} else if (key.isReadable()) {
// Xử lý read
handleRead(key);
}
keyIterator.remove();
}
}
}
}
2.3 Path và Files
- Path API:
- Thay thế cho File class
- Xử lý đường dẫn linh hoạt
-
Hỗ trợ symbolic links
-
Files API:
- Các tiện ích xử lý file
- Hỗ trợ copy, move, delete
- Đọc/ghi file đơn giản
public class PathAndFilesExample {
public void demonstratePath() {
// Tạo Path
Path path1 = Paths.get("file.txt");
Path path2 = Paths.get("/home/user/file.txt");
// Thao tác với Path
System.out.println(path2.getFileName());
System.out.println(path2.getParent());
System.out.println(path2.getRoot());
// Normalize path
Path path3 = Paths.get("/home/./user/../file.txt");
System.out.println(path3.normalize());
}
public void demonstrateFiles() throws IOException {
Path path = Paths.get("file.txt");
// Đọc tất cả các dòng
List<String> lines = Files.readAllLines(path);
// Đọc tất cả bytes
byte[] bytes = Files.readAllBytes(path);
// Ghi file
Files.write(path, "Content".getBytes());
// Copy file
Path source = Paths.get("source.txt");
Path target = Paths.get("target.txt");
Files.copy(source, target,
StandardCopyOption.REPLACE_EXISTING);
// Move file
Files.move(source, target,
StandardCopyOption.REPLACE_EXISTING);
// Delete file
Files.delete(path);
// hoặc
Files.deleteIfExists(path);
}
}
3. Asynchronous I/O (NIO.2)
Khái Niệm
- Asynchronous I/O:
- I/O không đồng bộ hoàn toàn
- Callback-based programming
- Hiệu quả cho high-throughput
3.1 AsynchronousFileChannel
- Đặc điểm:
- Hỗ trợ đọc/ghi không đồng bộ
- Sử dụng Future hoặc CompletionHandler
- Không block thread chính
public class AsyncIOExample {
public void readAsync(String path) throws IOException {
Path file = Paths.get(path);
AsynchronousFileChannel channel =
AsynchronousFileChannel.open(file);
ByteBuffer buffer = ByteBuffer.allocate(1024);
Future<Integer> result = channel.read(buffer, 0);
while (!result.isDone()) {
// Thực hiện các công việc khác
}
buffer.flip();
byte[] data = new byte[buffer.limit()];
buffer.get(data);
System.out.println(new String(data));
channel.close();
}
public void readWithCallback(String path) throws IOException {
Path file = Paths.get(path);
AsynchronousFileChannel channel =
AsynchronousFileChannel.open(file);
ByteBuffer buffer = ByteBuffer.allocate(1024);
channel.read(buffer, 0, buffer,
new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result,
ByteBuffer attachment) {
attachment.flip();
byte[] data = new byte[attachment.limit()];
attachment.get(data);
System.out.println(new String(data));
}
@Override
public void failed(Throwable exc,
ByteBuffer attachment) {
exc.printStackTrace();
}
});
}
}
4. Best Practices và Performance
4.1 Best Practices
- Nguyên tắc cơ bản:
- Luôn đóng resources
- Sử dụng try-with-resources
- Chọn buffer size phù hợp
- Xử lý exceptions đúng cách
public class IOBestPractices {
// 1. Sử dụng try-with-resources
public void correctResourceHandling(String path) {
try (BufferedReader reader = new BufferedReader(
new FileReader(path))) {
// Xử lý file
} catch (IOException e) {
e.printStackTrace();
}
}
// 2. Sử dụng buffer cho hiệu năng tốt
public void efficientReading(String path) {
try (BufferedInputStream bis = new BufferedInputStream(
new FileInputStream(path))) {
byte[] buffer = new byte[8192]; // 8KB buffer
int bytesRead;
while ((bytesRead = bis.read(buffer)) != -1) {
// Xử lý dữ liệu
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
4.2 Performance Tips
- Tối ưu hiệu năng:
- Sử dụng NIO cho nhiều connections
- Chọn buffer size phù hợp
- Sử dụng DirectBuffer cho large files
- Tránh frequent disk access
```java public class IOPerformanceTips { // 1. Sử dụng NIO cho nhiều connections public void handleMultipleConnections() throws IOException { Selector selector = Selector.open(); ServerSocketChannel serverSocket = ServerSocketChannel.open(); serverSocket.bind(new InetSocketAddress(5000)); serverSocket.configureBlocking(false); serverSocket.register(selector, SelectionKey.OP_ACCEPT);
// Non-blocking I/O
while (true) {
selector.select();
// Xử lý các channels sẵn sàng
}
}
// 2. Sử dụng BufferedWriter cho writing performance
public void efficientWriting(String path,
List<String> lines) {
try (BufferedWriter writer = new BufferedWriter(
new FileWriter(path))) {
for (String line : lines) {
writer.write(line);
writer.newLine();
}
// Buffer sẽ tự động flush khi đầy
} catch (IOException e) {
e.printStackTrace();
}
}
// 3. Sử dụng DirectBuffer cho large files
public void handleLargeFiles(String path)
throws IOException {
try (FileChannel channel = FileChannel.open(
Paths.get(path), StandardOpenOption.READ)) {
// Direct buffer bypass JVM heap
ByteBuffer buffer =
ByteBuffer.allocateDirect(1024 * 1024); // 1MB
while (channel.read(buffer) != -1) {
buffer.flip();
// Process data
buffer.clear();
}
}
}
}