Khái niệm nền tảng về luồng (Stream)
Trong môi trường Java, mọi hoạt động trao đổi dữ liệu giữa bộ nhớ RAM (JVM Heap) và thiết bị lưu trữ bên ngoài (đĩa cứng, network...) đều thông qua một đối tượng trung gian gọi là Luồng (Stream). Coi như một đường ống dẫn dữ liệu, luồng cho phép chương trình đọc thông tin từ nguồn vào bộ nhớ hoặc ghi thông tin từ bộ nhớ ra thiết bị đích.
Phân loại Luồng
Các luồng trong Java thường được phân nhóm dựa trên ba tiêu chí kỹ thuật chính:
- Theo hướng truyền: Xác định dựa trên vị trí của JVM. Luồng nhập (
InputStream) mang dữ liệu từ thiết bị lưu trữ vào RAM. Luồng xuất (OutputStream) đẩy dữ liệu từ RAM ra ngoài. - Theo đơn vị xử lý: Luồng byte hoạt động ở cấp độ byte, hỗ trợ mọi định dạng tệp (ảnh, âm thanh, văn bản...). Luồng ký tự làm việc theo chuẩn Unicode/char, chuyên biệt chỉ dành cho dữ liệu dạng chữ thuần túy (.txt, .java, .xml...).
- Theo vai trò chức năng: Luồng nút (
Node Stream) thực hiện thao tác đọc/ghi trực tiếp lên tài nguyên hệ thống. Luồng lọc (Filter Stream) đóng vai trò bao bọc, bổ sung tính năng mở rộng cho luồng nút. Kiến trúc này mô phỏng rõ ràng mẫu thiết kế Decorator (Trang trí), giúp kết hợp các chức năng linh hoạt mà không cần sửa đổi lớp gốc.
Họ Luồng Byte Cơ Bản
Nền tảng xử lý byte nằm ở hai lớp trừu tượng:
java.io.InputStream: Lớp cha của mọi luồng nhập byte. Phương thứcread()trả về mã số của byte tiếp theo, hoặc-1khi hết dữ liệu.java.io.OutputStream: Lớp cha của mọi luồng xuất byte. Các phương thức ghi dữ liệu cho phép truyền int, mảng byte hoặc xác định vùng ghi cụ thể.
Lưu ý: Hai lớp này mang tính tổng quát hóa và không tồn tại dưới dạng luồng nút hay lọc cụ thể.
Thực thi Ghi Tệp Với FileOutputStream
Để ghi dữ liệu xuống đĩa, ta khởi tạo đối tượng FileOutputStream. Dưới đây là ví dụ minh họa cách chuyển đổi chuỗi thành mảng byte và ghi ra đĩa, kèm theo quy trình quản lý tài nguyên an toàn:
package vn.tech.io.demo;
import java.io.FileOutputStream;
import java.io.IOException;
public class DataWriterExample {
public static void main(String[] args) {
FileOutputStream byteWriter = null;
try {
// Chỉ định đường dẫn đích, hệ thống sẽ tự tạo tệp nếu chưa có
byteWriter = new FileOutputStream("report_data.bin");
String payload = "HelloJavaIOStreams";
byteWriter.write(payload.getBytes());
System.out.println("Ghi dữ liệu xong.");
} catch (IOException ioEx) {
ioEx.printStackTrace();
} finally {
// Luôn giải phóng tài nguyên hệ thống
if (byteWriter != null) {
try {
byteWriter.close();
} catch (IOException closeEx) {
closeEx.printStackTrace();
}
}
}
}
}
Đọc Dữ Liệu Với FileInputStream
Thao tác ngược lại sử dụng FileInputStream. Luồng này hỗ trợ nạp dữ liệu vào buffer cục bộ để xử lý từng phần:
package vn.tech.io.demo;
import java.io.FileInputStream;
import java.io.IOException;
public class DataReaderExample {
public static void main(String[] args) throws IOException {
FileInputStream byteReader = new FileInputStream("report_data.bin");
byte[] buffer = new byte[128]; // Kích thước đệm tùy chỉnh
int bytesRead;
// Vòng lặp chờ đọc đến khi gặp ký hiệu kết thúc (-1)
while ((bytesRead = byteReader.read(buffer)) != -1) {
// Chuyển vùng dữ liệu thực tế đã đọc thành chuỗi in ra màn hình
System.out.print(new String(buffer, 0, bytesRead));
}
byteReader.close(); // Đóng luồng khi hoàn tất
}
}
Mô hình Sao chép Tập Tin
Nguyên tắc copy file đơn giản là sự kết hợp song song của hai luồng trên. Dữ liệu được tách nhỏ, lần lượt chảy từ nguồn đầu vào -> JVM Heap -> Đích đầu ra. Tốc độ sao chép phụ thuộc đáng kể vào kích thước buffer và cách quản lý ngoại lệ trong quá trình di chuyển khối dữ liệu. Đối với nhu cầu ghi/đọc kiểu dữ liệu cấu trúc phức tạp hơn (int, double, String), hệ thống cung cấp thêm tầng lọc DataInputStream và DataOutputStream nhằm hỗ trợ serialize/deserialize trực tiếp mà không cần chuyển đổi thủ công sang byte array.