Luồng Đệm
(Buffered Streams)

Trong các ví dụ trước, chúng ta đã làm việc với các luồng I/O không đệm (unbuffered), nơi mỗi yêu cầu đọc hoặc ghi dữ liệu được xử lý trực tiếp bởi hệ điều hành. Điều này có thể dẫn đến hiệu suất kém, vì mỗi thao tác I/O thường kích hoạt các hành động tốn kém như truy cập đĩa, kết nối mạng, hoặc các tài nguyên khác.

Để cải thiện hiệu suất, Java cung cấp các luồng I/O đệm (buffered streams). Với luồng đầu vào đệm, dữ liệu được đọc vào một bộ đệm trong bộ nhớ trước khi được xử lý, giúp giảm số lần gọi đến API đầu vào của hệ điều hành. Tương tự, luồng đầu ra đệm ghi dữ liệu vào một bộ đệm, và chỉ gọi đến API đầu ra khi bộ đệm đã đầy, tối ưu hóa quá trình ghi dữ liệu.

Sử dụng luồng đệm (Buffered Streams)

Bạn có thể dễ dàng chuyển đổi một luồng không đệm thành luồng đệm bằng cách sử dụng kỹ thuật gói (wrapping) mà chúng ta đã áp dụng nhiều lần trước đây. Trong kỹ thuật này, đối tượng luồng không đệm được truyền vào constructor của một lớp luồng đệm.

Kỹ thuật gói (wrapping) trong lập trình là quá trình bao bọc hoặc bao gói một đối tượng bên trong một đối tượng khác để mở rộng hoặc thay đổi chức năng của nó. Trong ngữ cảnh của Java I/O, kỹ thuật gói liên quan đến việc sử dụng một luồng đệm (buffered stream) để bao bọc một luồng không đệm (unbuffered stream). Điều này cho phép bạn tận dụng các tính năng bổ sung của luồng đệm, chẳng hạn như tăng hiệu suất thông qua việc lưu trữ dữ liệu tạm thời trong bộ nhớ.

Ví dụ: Bạn có thể gói một FileInputStream (luồng không đệm) bằng một BufferedInputStream (luồng đệm) như sau:

Ví dụ

FileInputStream fileInput = new FileInputStream("Example.txt");
BufferedInputStream bufferedInput = new BufferedInputStream(fileInput);
Hoặc
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("Example.txt"))

Các lớp luồng đệm (Buffered Streams)

Java cung cấp bốn lớp luồng đệm để gói các luồng không đệm:

↳ BufferedInputStream và BufferedOutputStream: Tạo ra các luồng byte đệm.

↳ BufferedReader và BufferedWriter: Tạo ra các luồng ký tự đệm.

Bây giờ, chúng ta sẽ đi sâu vào khám phá chi tiết từng lớp trong luồng đệm (buffered stream) trong Java, bao gồm các lớp chính được sử dụng để thực hiện các thao tác đầu vào và đầu ra liên quan đến dữ liệu dạng byte, ký tự.

Lớp BufferedInputStream (Class BufferedInputStream)

Lớp BufferedInputStream là một lớp trong Java được sử dụng để đọc dữ liệu từ một luồng byte (input stream) với hiệu suất cải thiện thông qua việc sử dụng bộ đệm. Nó thực hiện điều này bằng cách sử dụng một bộ đệm nội bộ để lưu trữ dữ liệu đọc được từ luồng gốc. Lớp này kế thừa từ lớp FilterInputStream và cung cấp một cách tiếp cận hiệu quả hơn để đọc dữ liệu từ các nguồn như tập tin hoặc mạng.

Khai báo lớp BufferedInputStream trong Java

Để sử dụng lớp BufferedInputStream, 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.BufferedInputStream;

Cú pháp khai báo lớp BufferedInputStream:

Cú pháp

public class BufferedInputStream
extends FilterInputStream

Dưới đây là giải thích chi tiết về cú pháp khai báo này:

↳ public class BufferedInputStream: Đây là khai báo một lớp công khai (public) tên là BufferedInputStream. 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 FilterInputStream: Lớp BufferedInputStream kế thừa từ lớp FilterInputStream. FilterInputStream là một lớp trừu tượng cung cấp cơ chế để tạo các lớp "filter" (bộ lọc) cho luồng đầu vào. Lớp này giúp thêm tính năng mới hoặc mở rộng tính năng của các luồng đầu vào hiện có.

Các trường trong lớp BufferedInputStream

Lớp BufferedInputStream sử dụng một số trường để quản lý bộ đệm và trạng thái đọc:

↳ protected byte[] buf: Mảng byte nội bộ dùng để lưu trữ dữ liệu đọc được từ luồng gốc.

↳ protected int count: Chỉ số tiếp theo sau ký tự cuối cùng trong bộ đệm.

↳ protected int marklimit: Giới hạn số byte có thể đọc trước khi đánh dấu bị mất hiệu lực..

↳ protected int markpos: Vị trí trong bộ đệm tại thời điểm gọi phương thức mark.

↳ protected int pos: Vị trí hiện tại trong bộ đệm.

Các constructor của lớp BufferedInputStream

Lớp BufferedInputStream cung cấp hai constructor để tạo đối tượng và thiết lập kích thước bộ đệm:

↳ BufferedInputStream(InputStream in): Tạo một BufferedInputStream với kích thước bộ đệm mặc định, sử dụng luồng đầu vào in làm nguồn dữ liệu.

↳ BufferedInputStream(InputStream in, int size): Tạo một BufferedInputStream với kích thước bộ đệm được chỉ định, sử dụng luồng đầu vào in làm nguồn dữ liệu.

Các phương thức của lớp BufferedInputStream

Các phương thức trong BufferedInputStream cung cấp nhiều cách để đọc dữ liệu từ một luồng. Bạn có thể sử dụng chúng để đọc dữ liệu một cách hiệu quả nhờ bộ đệm, bỏ qua các byte không cần thiết, và đặt lại vị trí đọc trong luồng. Dưới đây là danh sách tất cả các phương thức của lớp BufferedInputStream trong Java:

↳ 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 hệ thống liên quan.

↳ void mark(int readlimit): Đánh dấu vị trí hiện tại trong luồng để có thể quay lại sau này bằng phương thức reset().

↳ boolean markSupported(): Trả về true vì BufferedInputStream hỗ trợ đánh dấu.

↳ int read(): Đọc một byte từ luồng. Trả về giá trị của byte hoặc -1 nếu đã đến cuối luồng.

↳ int read(byte[] b, int off, int len): Đọc tối đa len byte vào mảng b bắt đầu từ vị trí off. Trả về số lượng byte đã đọc hoặc -1 nếu đã đến cuối luồng.

↳ void reset(): Đặt lại vị trí của luồng về vị trí đã được đánh dấu bằng phương thức mark().

↳ long skip(long n): Bỏ qua n byte trong luồng. Trả về số lượng byte thực sự đã bỏ qua.

Ví dụ 1: Đọc dữ liệu từng Byte

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.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;

public class Example {
    public static void main(String[] args) {
        try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("Example.txt"))) {
            int byteData;
            while ((byteData = bis.read()) != -1) {
                System.out.print((char) byteData); // In ra từng ký tự
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Kết quả của chương trình là:

Hello, world!

Trong ví dụ trên, chúng ta Mở tập tin "Example.txt" bằng FileInputStream. Sử dụng BufferedInputStream để đệm dữ liệu từ tập tin và đọc từng byte từ BufferedInputStream và chuyển đổi nó thành ký tự để in ra màn hình.

Ví dụ 2: Đọc dữ liệu vào mảng Byte

Ví dụ: Example.java

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;

public class Example {
    public static void main(String[] args) {
        try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("Example.txt"))) {
            byte[] buffer = new byte[1024]; // Mảng byte để lưu trữ dữ liệu đọc
            int bytesRead;
            while ((bytesRead = bis.read(buffer)) != -1) {
                System.out.write(buffer, 0, bytesRead); // In ra dữ liệu đọc
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Kết quả của chương trình là:

Hello, world!

Trong ví dụ trên, chúng ta tạo một mảng byte có kích thước 1024 để lưu trữ dữ liệu đọc. Đọc dữ liệu từ BufferedInputStream vào mảng buffer và sử dụng System.out.write để in dữ liệu ra màn hình.

Ví dụ 3: Kiểm tra số Byte có sẵn để đọc

Ví dụ: Example.java

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;

public class Example {
    public static void main(String[] args) {
        try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("Example.txt"))) {
            // In ra số byte có sẵn để đọc
            System.out.println("Số byte có sẵn: " + bis.available());

            int byteData;
            while ((byteData = bis.read()) != -1) {
                System.out.print((char) byteData); // In ra từng ký tự
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Kết quả của chương trình là:

Số byte có sẵn: 68
Hello, world!

Trong ví dụ trên, chúng ta Sử dụng phương thức available để kiểm tra số byte còn lại có sẵn để đọc từ bộ đệm. Sau đó, đọc và in tất cả dữ liệu từ tập tin.

Câu Nói Truyền Cảm Hứng

“Bắt đầu ở đâu không quan trọng, quan trọng là bạn sẵn sàng bắt đầu.” – W. Clement Stone

Không Gian Tích Cực

“Chúc bạn luôn giữ vững niềm tin và sức mạnh để vượt qua mọi thử thách trong cuộc sống.”