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();
        }
    }
}

}