Luồng Byte
(Byte Streams)
Luồng Byte (Byte Streams) trong Java là một loại luồng dùng để xử lý dữ liệu nhị phân dưới dạng byte 8 bit. Chúng cung cấp các phương thức để đọc và ghi dữ liệu theo cấp độ byte, chúng cho phép bạn đọc và ghi dữ liệu dạng nhị phân, như hình ảnh, âm thanh, hoặc dữ liệu từ các tệp nhị phân.
Dữ liệu byte là dữ liệu nhị phân thô, không liên quan đến các ký tự văn bản. Mỗi byte chứa 8 bit dữ liệu, và nó có thể là bất kỳ loại dữ liệu nào như hình ảnh, âm thanh, hoặc văn bản mã hóa
Tất cả các lớp luồng byte đều kế thừa từ hai lớp gốc là InputStream (luồng đầu vào) và OutputStream (luồng đầu ra). Có nhiều lớp luồng byte khác nhau. Ví dụ điển hình để minh họa hoạt động của luồng byte là các lớp FileInputStream và FileOutputStream chuyên dùng cho thao tác đọc/ghi file, ByteArrayInputStream và ByteArrayOutputStream được sử dụng để đọc/ghi dữ liệu vào mảng byte với bộ nhớ đệm. Các loại luồng byte khác hoạt động tương tự, chủ yếu khác nhau về cách tạo. Bây giờ Chúng ta sẽ khám phá chi tiết từng lớp trong luồng byte trong Java, bao gồm các lớp chính được sử dụng để thực hiện các hoạt động đầu vào và đầu ra liên quan đến dữ liệu nhị phân.
Lớp FileInputStream (Class FileInputStream)
FileInputStream là một lớp trong Java dùng để đọc dữ liệu từ các file dưới dạng byte. Đây là một phần của luồng byte và thuộc gói java.io.
FileInputStream lấy dữ liệu đầu vào từ một file trong hệ thống file. Các file có sẵn để đọc phụ thuộc vào môi trường hệ thống mà chương trình đang chạy. FileInputStream được thiết kế để đọc dòng dữ liệu thô (raw byte) từ file, chẳng hạn như dữ liệu hình ảnh. Nếu bạn cần đọc dữ liệu dưới dạng ký tự (text), nên sử dụng lớp FileReader thay vì FileInputStream.
Khai báo lớp FileInputStream trong Java
Để sử dụng lớp FileInputStream, bạn cần import gói java.io bạn cần thêm câu lệnh import vào đầu file Java của mình. Gói này cung cấp các lớp và giao diện để thực hiện các hoạt động nhập xuất (I/O) trong Java.
Cú pháp câu lệnh import:
Cú pháp
import java.io.FileInputStream;
Cú pháp khai báo lớp FileInputStream:
Cú pháp
public class FileInputStream extends InputStream
Dưới đây là giải thích chi tiết về cú pháp khai báo này:
↳ public class FileInputStream: Đây là khai báo một lớp công khai (public) tên là FileInputStream. Lớp này có thể được truy cập từ bất kỳ đâu trong dự án Java, miễn là nó được import hoặc ở cùng gói.
↳ extends InputStream: Lớp FileInputStream kế thừa từ lớp InputStream. Điều này có nghĩa là FileInputStream là một lớp con của InputStream và kế thừa tất cả các phương thức và thuộc tính của InputStream.
Các constructor của lớp FileInputStream
Lớp FileInputStream có ba constructor chính để tạo đối tượng và mở kết nối đến một file:
↳ FileInputStream(File file): Tạo một đối tượng FileInputStream để mở kết nối đến một file thực tế được định nghĩa bởi đối tượng File là file trong hệ thống file.
↳ FileInputStream(FileDescriptor fdObj): Tạo một đối tượng FileInputStream bằng cách sử dụng file descriptor fdObj, đại diện cho một kết nối hiện có đến một file thực tế trong hệ thống file.
↳ FileInputStream(String name): Tạo một đối tượng FileInputStream bằng cách mở kết nối đến một file thực tế, được định nghĩa bởi tên đường dẫn name trong hệ thống file.
Lưu ý: Các constructor này đều có thể ném ngoại lệ FileNotFoundException nếu file không tồn tại hoặc không thể truy cập. Nên sử dụng khối try-catch để xử lý ngoại lệ này.
Các phương thức của lớp FileInputStream
Lớp FileInputStream cung cấp một số phương thức để tương tác với dữ liệu trong file. Dưới đây là tóm tắt các phương thức chính của lớp FileInputStream:
↳ int available(): Trả về ước lượng số byte có thể đọc được từ luồng mà không bị chặn.
↳ void close(): Đóng luồng và giải phóng tài nguyên.
↳ protected void finalize(): Phương thức đã lỗi thời, không nên sử dụng.
↳ FileChannel getChannel(): Trả về đối tượng FileChannel liên kết với luồng này.
↳ FileDescriptor getFD(): Trả về đối tượng FileDescriptor đại diện cho kết nối với file.
↳ int read(): Đọc một byte dữ liệu từ luồng và trả về nó dưới dạng số nguyên.
↳ int read(byte[] b): Đọc tối đa b.length byte dữ liệu vào mảng b.
↳ int read(byte[] b, int off, int len): Đọc tối đa lên byte dữ liệu vào mảng b bắt đầu từ vị trí off.
↳ long skip(long n): Bỏ qua n byte dữ liệu trong luồng.
Ví dụ 1
Dưới đây là một ví dụ đơn giản về cách sử dụng FileInputStream để đọc nội dung từ một file văn bản. Trong ví dụ này, chúng ta sẽ đọc các byte từ một file và in chúng ra màn hình.
Giả sử chúng ta có một file văn bản tên là example.txt chứa nội dung là "Hello, world!".
Ví dụ: Example.java
import java.io.FileInputStream;
import java.io.IOException;
public class Example {
public static void main(String[] args) {
// Đường dẫn đến file cần đọc
String filePath = "example.txt";
// Sử dụng try-with-resources để tự động đóng FileInputStream
try (FileInputStream fileInputStream = new FileInputStream(filePath)) {
int byteRead;
// Đọc dữ liệu từ file từng byte một
while ((byteRead = fileInputStream.read()) != -1) {
// In ra ký tự tương ứng với byte đọc được
System.out.print((char) byteRead);
}
} catch (IOException e) {
// Xử lý lỗi khi đọc file
e.printStackTrace();
}
}
}
Kết quả của chương trình là:
Trong ví dụ trên, chúng tôi đã sử dụng cấu trúc try-with-resources để đảm bảo FileInputStream được đóng tự động. Khi bạn sử dụng try-with-resources, Java sẽ tự động gọi phương thức close() cho các tài nguyên, bao gồm FileInputStream, khi khối try kết thúc. Điều này giúp tránh việc quên đóng tài nguyên, giảm nguy cơ rò rỉ tài nguyên.
Tuy nhiên, nếu bạn muốn thấy cách đóng file một cách rõ ràng hơn, bạn có thể sử dụng cấu trúc try-finally.
Ví dụ 2
Dưới đây là một ví dụ đơn giản về cách sử dụng FileInputStream để đọc một ký tự từ một file và in ra ký tự đó cùng với giá trị byte của nó.
Giả sử chúng ta có một file văn bản tên là example.txt chứa nội dung là "Hello, world!".
Ví dụ: Example.java
import java.io.FileInputStream;
import java.io.IOException;
public class Example {
public static void main(String[] args) {
// Đường dẫn đến file cần đọc
String filePath = "example.txt";
// Sử dụng try-with-resources để tự động đóng FileInputStream
try (FileInputStream fileInputStream = new FileInputStream(filePath)) {
// Đọc ký tự đầu tiên từ file
int character = fileInputStream.read();
// Kiểm tra nếu ký tự không phải là -1 (cuối file)
if (character != -1) {
// Chuyển đổi byte đọc được thành ký tự và in ra màn hình
System.out.println("Ký tự đọc được: " + (char) character + " \nByte đọc được: " + (byte) character );
} else {
System.out.println("File rỗng hoặc đã đọc hết nội dung.");
}
} catch (IOException e) {
// Xử lý lỗi khi đọc file
e.printStackTrace();
}
}
}
Kết quả của chương trình là:
Byte đọc được: 72
Lý do phương thức read() trả về int:
↳ Một byte có thể có giá trị từ 0 đến 255 (hoặc từ -128 đến 127 trong ký hiệu dấu). Trả về giá trị dưới dạng int giúp bao phủ toàn bộ phạm vi giá trị byte có thể và tránh các vấn đề với kiểu dữ liệu byte.
↳ Khi đọc một byte và trả về dưới dạng int, nó dễ dàng hơn để xử lý và chuyển đổi giá trị. Ví dụ, bạn có thể chuyển đổi giá trị int đọc được thành ký tự bằng cách cast (ép kiểu) hoặc sử dụng các phương thức xử lý dữ liệu khác.
↳ Nếu đã đến cuối luồng (EOF - End of File), phương thức trả về -1. Giá trị -1 được sử dụng để chỉ rằng đã đến cuối luồng. Điều này không thể biểu diễn bằng một giá trị byte hợp lệ (0-255), vì vậy giá trị -1 là một cách để phân biệt kết thúc luồng.