Thứ Bảy, 20 tháng 4, 2024

I/O

 

Mục lục:

A. Phân loại

1. Dựa trên Stream
2. Theo nơi đọc/ ghi dữ liệu
3. Theo mục đích sử dụng
4. Theo loại luồng

B. Các thao tác với file

1. Ghi nội dung vào file
2. Đọc nội dung từ file

--------------------------------

Input/Output (I/O) là một khía cạnh quan trọng của bất kỳ ứng dụng nào, cho phép chúng ta truy nhập và truy xuất dữ liệu từ các nguồn và đích khác nhau như tệp tin, bộ nhớ, mạng, và thiết bị ngoại vi. Trong Java, I/O thường được thực hiện thông qua gói java.io hoặc java.nio.

Dưới đây là một số khái niệm cơ bản về I/O trong Java:

Khái niệm Mô tả
Input/Output StreamsLuồng dữ liệu byte để đọc và ghi dữ liệu. InputStream và OutputStream là các lớp trừu tượng cơ bản.
Readers và WritersLuồng dữ liệu ký tự để đọc và ghi dữ liệu dưới dạng ký tự. Reader và Writer là các lớp trừu tượng tương ứng.
File I/OQuá trình đọc và ghi dữ liệu từ và đến tệp tin. Sử dụng các lớp như FileInputStream, FileOutputStream, FileReader, và FileWriter.
Buffered I/OCơ chế bộ đệm để tăng hiệu suất đọc và ghi dữ liệu bằng cách lưu trữ dữ liệu tạm thời trong bộ nhớ. Sử dụng các lớp như BufferedInputStream, BufferedOutputStream, BufferedReader, và BufferedWriter.
Network I/OQuá trình truy nhập và truy xuất dữ liệu từ và đến các nguồn trên mạng. Sử dụng các lớp như Socket, ServerSocket, và URLConnection.
Data I/OQuá trình đọc và ghi dữ liệu dạng nguyên thủy như int, float, double, và boolean. Sử dụng các lớp như DataInputStream và DataOutputStream.


A. Phân loại

1. Phân loại dựa trên Stream

"Stream" là một khái niệm quan trọng để biểu diễn luồng dữ liệu liên tục, nơi dữ liệu được truyền từ một nguồn đến một đích một cách tuần tự và liên tục. Dữ liệu trong luồng được xử lý một lượng nhỏ tại mỗi thời điểm, không cần phải đợi toàn bộ dữ liệu được tạo ra hoặc đọc vào bộ nhớ trước khi bắt đầu xử lý.

Dưới đây là một số điểm quan trọng về khái niệm stream:

Khái niệmMô tả
Luồng Đầu Vào (Input Stream)Sử dụng để đọc dữ liệu từ một nguồn (ví dụ: tệp tin, socket, hoặc bộ nhớ) vào chương trình.
Luồng Đầu Ra (Output Stream)Sử dụng để ghi dữ liệu từ chương trình ra ngoài (ví dụ: tệp tin, socket, hoặc bộ nhớ).
Xử Lý Tuần TựDữ liệu trong stream được xử lý một phần nhỏ tại mỗi thời điểm và tiếp tục cho đến khi tất cả dữ liệu được xử lý.
Dữ Liệu Tuần TựDữ liệu trong stream được xử lý tuần tự, từ đầu đến cuối, không truy cập ngẫu nhiên vào các phần của dữ liệu.
Cấu Trúc Tuần TựStream thường được xem như là một dãy tuần tự của dữ liệu, phù hợp để xử lý dữ liệu dạng tập hợp hoặc dạng dòng.
Phù Hợp Với I/OStream được sử dụng để thực hiện hoạt động Input/Output (I/O), dữ liệu được truyền từ nguồn đến đích một cách tuần tự và liên tục.
Sử dụng stream trong JavaGói java.io cung cấp nhiều lớp và giao diện để thực hiện các hoạt động I/O bằng cách sử dụng stream, đảm bảo hiệu quả và linh hoạt trong xử lý dữ liệu.

Dưới đây là một số lớp và giao diện sử dụng stream:

Lớp và Giao Diện cho Đầu Vào (Input):

Lớp/Giao DiệnMô Tả
InputStreamLớp trừu tượng cơ bản để đọc dữ liệu byte từ một nguồn.
ReaderLớp trừu tượng cơ bản để đọc dữ liệu ký tự từ một nguồn.
FileInputStreamLớp con của InputStream để đọc dữ liệu từ một tập tin.
ByteArrayInputStreamLớp con của InputStream để đọc dữ liệu từ một mảng byte.
BufferedInputStreamLớp con của InputStream để đọc dữ liệu với bộ đệm.
FileReaderLớp con của Reader để đọc dữ liệu từ một tập tin văn bản.
InputStreamReaderLớp con của Reader để đọc dữ liệu từ một luồng byte và chuyển đổi sang ký tự.
BufferedReaderLớp con của Reader để đọc dữ liệu với bộ đệm và hiệu quả hơn.

Lớp và Giao Diện cho Đầu Ra (Output):

Lớp/Giao DiệnMô Tả
OutputStreamLớp trừu tượng cơ bản để ghi dữ liệu byte đến một đích.
WriterLớp trừu tượng cơ bản để ghi dữ liệu ký tự đến một đích.
FileOutputStreamLớp con của OutputStream để ghi dữ liệu vào một tập tin.
ByteArrayOutputStreamLớp con của OutputStream để ghi dữ liệu vào một mảng byte.
BufferedOutputStreamLớp con của OutputStream để ghi dữ liệu với bộ đệm và hiệu quả hơn.
FileWriterLớp con của Writer để ghi dữ liệu vào một tập tin văn bản.
OutputStreamWriterLớp con của Writer để ghi dữ liệu từ ký tự sang byte và ghi vào một luồng byte.
BufferedWriterLớp con của Writer để ghi dữ liệu với bộ đệm và hiệu quả hơn.

Giao Diện Cho Việc Đóng Tài Nguyên:

Giao DiệnMô Tả
CloseableGiao diện đại diện cho các luồng hoặc nguồn tài nguyên có thể được đóng sau khi sử dụng.
AutoCloseableGiao diện tương tự như Closeable nhưng được sử dụng trong các tình huống try-with-resources để tự động đóng luồng hoặc giải phóng tài nguyên.

Dưới đây là các lớp mà không sử dụng stream để thực hiện các hoạt động Input/Output :

LớpMục ĐíchCách Thực Hiện I/O
StringXử lý chuỗi ký tự và mã hóaSử dụng phương thức như getBytes()getBytes(Charset charset) để chuyển đổi chuỗi thành mảng byte hoặc mã hóa dữ liệu dưới dạng byte.
ScannerĐọc dữ liệu từ nguồn dữ liệu (ví dụ: System.in)Sử dụng phương thức như next(), nextInt(), và nextLine() để đọc dữ liệu từng token hoặc dòng một cách tuần tự.
RandomAccessFileĐọc và ghi dữ liệu từ và đến một vị trí xác định trong tệp tinSử dụng phương thức như read()write() để đọc và ghi dữ liệu trực tiếp từ và đến vị trí cụ thể trong tệp tin.
File và PathThao tác với tệp và thư mục trong hệ thống tệpSử dụng phương thức như createNewFile(), delete(), và listFiles() để tạo, xóa, và liệt kê các tệp và thư mục.

2.Phân loại theo nơi đọc/ ghi dữ liệu

LớpMô tảNơi Đọc/Ghi Dữ Liệu
BufferedInputStreamBufferedOutputStreamMột luồng đệm cho phép đọc/ghi dữ liệu từ/vào một luồng byte với hiệu suất cao hơn.Byte Stream
BufferedReaderBufferedWriterĐọc/Ghi dữ liệu từ/vào một luồng ký tự (character stream) với khả năng đệm để cung cấp hiệu suất tốt hơn.Character Stream
ByteArrayInputStreamByteArrayOutputStreamMột luồng đọc/ghi dữ liệu từ/vào một mảng byte.Byte Array
FileReaderFileWriterĐọc/Ghi dữ liệu từ/vào một tệp văn bản bằng cách sử dụng một bộ mã ký tự được cài đặt sẵn.Text File
FileInputStreamFileOutputStreamĐọc/Ghi dữ liệu từ/vào một tệp trong hệ thống tệp.File
ObjectInputStreamObjectOutputStreamĐọc/Ghi các đối tượng Java từ/vào một luồng byte.Serialized Object
PipedInputStreamPipedOutputStreamĐọc/Ghi dữ liệu từ/vào một ống thông tin dùng để truyền dữ liệu giữa hai luồng trong cùng một chương trình.Pipe
PushbackInputStreamPushbackReaderMột luồng đặc biệt cho phép "đẩy ngược" một byte hoặc ký tự trở lại luồng đọc để đọc lại sau này.Byte/Character Stream
SequenceInputStreamBiểu diễn sự kết hợp tuần tự của các luồng đầu vào, cho phép đọc dữ liệu từ nhiều nguồn liên tiếp nhau.Byte Stream
FilterInputStreamFilterOutputStreamLớp trừu tượng cơ sở cho các lớp lọc (filter) để mở rộng chức năng của luồng đầu vào và đầu ra.Byte Stream
InputStreamOutputStreamLớp trừu tượng cơ sở cho tất cả các luồng đầu vào và đầu ra.Abstract
PrintStreamPrintWriterCho phép ghi dữ liệu được định dạng vào một luồng đầu ra.Abstract
DataInputStreamDataOutputStreamĐọc và ghi dữ liệu theo các kiểu dữ liệu nguyên thủy Java từ/vào một luồng byte.Byte Stream
FileFileDescriptorĐại diện cho đường dẫn tệp và miêu tả tệp hoặc nguồn/suất byte khác.System
RandomAccessFileCho phép đọc và ghi dữ liệu từ một tệp mà bạn có thể di chuyển ngẫu nhiên đến các vị trí cụ thể trong tệp.File
ReaderWriterLớp trừu tượng cơ sở cho tất cả các lớp đọc và ghi ký tự.Abstract
CharArrayReaderCharArrayWriterCho phép đọc và ghi dữ liệu từ/vào một mảng ký tự.Memory
StringReaderStringWriterCho phép đọc và ghi dữ liệu từ/vào một chuỗi.Memory
ConsoleCung cấp các phương thức để truy cập thiết bị giao diện dựa trên ký tự (console) được liên kết với máy ảo Java hiện tại.System Console
StreamTokenizerPhân tích một luồng ký tự thành các token (từ hoặc số).Byte/Character Stream
SerializablePermissionCho phép quyền truy cập vào các lớp và phương thức khi tạo và đọc đối tượng Java Serialization.Serialization
FilePermissionĐại diện cho quyền truy cập vào tệp.System
FileFilterFilenameFilterCung cấp các giao diện để lọc các tệp trong một thư mục.File System

3.Phân loại theo mục đích sử dụng

Tên
Mục đích
Sử dụng khi
Ưu điểm
Nhược điểm
FileOutputStream và FileInputStream
Đọc và ghi dữ liệu nhị phân (binary data).
Làm việc với dữ liệu không phải văn bản (hình ảnh, âm thanh), hoặc khi cần ghi và đọc dữ liệu dạng byte.
Hiệu quả khi làm việc với dữ liệu nhị phân.
Khó khăn khi làm việc với dữ liệu văn bản, cần phải chuyển đổi thủ công giữa byte và ký tự.
FileWriter và FileReader
Đọc và ghi dữ liệu văn bản (text data).
Làm việc với dữ liệu văn bản như tệp tin văn bản, tệp tin cấu hình.
Dễ sử dụng cho dữ liệu văn bản, tự động xử lý chuyển đổi ký tự.
Không phù hợp cho dữ liệu nhị phân.
BufferedWriter và BufferedReader
Đọc và ghi dữ liệu văn bản với bộ đệm (buffered).
Cần tăng hiệu suất khi làm việc với tệp tin lớn hoặc khi thực hiện nhiều thao tác đọc/ghi.
Hiệu suất cao hơn so với FileWriter và FileReader nhờ vào việc sử dụng bộ đệm.
Cần thêm mã để đóng bộ đệm một cách chính xác.
ObjectOutputStream và ObjectInputStream
Đọc và ghi đối tượng Java dưới dạng tuần tự hóa (serialization).
Lưu trữ và khôi phục trạng thái của đối tượng Java.
Đơn giản hóa việc lưu trữ và khôi phục đối tượng, hỗ trợ đối tượng phức tạp.
Yêu cầu các lớp phải triển khai giao diện Serializable, dữ liệu không dễ đọc và chỉnh sửa thủ công.
BufferedOutputStream và BufferedInputStream
Đọc và ghi dữ liệu nhị phân với bộ đệm (buffered).
Cần tăng hiệu suất khi làm việc với dữ liệu nhị phân lớn hoặc khi thực hiện nhiều thao tác đọc/ghi.
Hiệu suất cao hơn so với FileOutputStream và FileInputStream nhờ vào việc sử dụng bộ đệm.
Cần thêm mã để đóng bộ đệm một cách chính xác.

Để hiểu rõ hơn về mục đích sử dụng, ta đến với ví dụ sau:

File: Student.java - Lớp này dùng để lưu thông tin cho mỗi sinh viên

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64

import java.io.Serializable;

public class Student implements Serializable {
    private int id;
    private String name;
    private byte age;
    private String address;
    private float gpa;
 
    public Student() {
    }
 
    public Student(int id, String name, byte age,
            String address, float gpa) {
        super();
        this.id = id;
        this.name = name;
        this.age = age;
        this.address = address;
        this.gpa = gpa;
    }
 
    public int getId() {
        return id;
    }
 
    public void setId(int id) {
        this.id = id;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public byte getAge() {
        return age;
    }
 
    public void setAge(byte age) {
        this.age = age;
    }
 
    public String getAddress() {
        return address;
    }
 
    public void setAddress(String address) {
        this.address = address;
    }
 
    public float getGpa() {
        return gpa;
    }
 
    public void setGpa(float gpa) {
        this.gpa = gpa;
    }
}

File: StudentDao.java - Lớp này dùng để đọc và ghi mỗi sinh viên vào file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61


public class StudentDao {
    private static final String STUDENT_FILE_NAME = "student.txt";
 
    public void write(List<Student> studentList) {
        FileOutputStream fos = null;
        ObjectOutputStream oos = null;
        try {
            fos = new FileOutputStream(new File(STUDENT_FILE_NAME));
            oos = new ObjectOutputStream(fos);
            oos.writeObject(studentList);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            closeStream(fos);
            closeStream(oos);
        }
    }
 
    public List<Student> read() {
        List<Student> studentList = new ArrayList<>();
        FileInputStream fis = null;
        ObjectInputStream ois = null;
        try {
            fis = new FileInputStream(new File(STUDENT_FILE_NAME));
            ois = new ObjectInputStream(fis);
            studentList = (List<Student>) ois.readObject();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            closeStream(fis);
            closeStream(ois);
        }
        return studentList;
    }

    private void closeStream(InputStream is) {
        if (is != null) {
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private void closeStream(OutputStream os) {
        if (os != null) {
            try {
                os.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

Nếu không sử dụng ObjectO/IputStream mà sử dụng FileStream :

Nếu bạn muốn thay đổi cách đọc và ghi file từ việc sử dụng ObjectStream sang sử dụng FileStream, bạn có thể sử dụng các phương thức của FileReader và FileWriter để đọc và ghi file dạng văn bản.

Trước tiên, bạn cần đảm bảo rằng lớp Student có thể được chuyển đổi sang dạng chuỗi (string) và ngược lại. Một cách đơn giản là tạo phương thức toString để chuyển đổi Student thành chuỗi và một phương thức tĩnh để chuyển đổi chuỗi ngược lại thành Student.




Giải thích

  1. Phương thức write:

    • Sử dụng BufferedWriter để ghi từng đối tượng Student dưới dạng chuỗi vào file.
    • Mỗi đối tượng Student được chuyển đổi thành chuỗi bằng phương thức toString và ghi vào file.
    • FileWriter giúp ghi dữ liệu vào file văn bản, và BufferedWriter giúp ghi dữ liệu một cách hiệu quả hơn bằng cách giảm số lần truy cập trực tiếp vào file hệ thống.
  2. Phương thức read:

    • Sử dụng BufferedReader để đọc từng dòng từ file.
    • Mỗi dòng được chuyển đổi ngược lại thành đối tượng Student bằng phương thức fromString.
    • FileReader giúp đọc dữ liệu từ file văn bản, và BufferedReader giúp đọc dữ liệu một cách hiệu quả hơn bằng cách giảm số lần truy cập trực tiếp vào file hệ thống.

Nhược điểm :

- Cần tự quản lý việc chuyển đổi đối tượng thành chuỗi và ngược lại.
- Mất đi lợi ích của việc tuần tự hóa đối tượng nếu bạn cần lưu trữ đối tượng phức tạp hơn.


Tại sao cần kết hợp FileOutputStream với ObjectOutputStream?

  • FileOutputStream:

    • Đây là lớp luồng (stream) cơ bản trong Java dùng để ghi dữ liệu thô (raw data) vào file.
    • Nó làm việc ở cấp độ byte, nghĩa là nó không biết cách xử lý các đối tượng hoặc kiểu dữ liệu phức tạp.
  • ObjectOutputStream:

    • Đây là lớp luồng cao cấp (high-level stream) dùng để ghi các đối tượng Java vào luồng đầu ra.
    • Nó thực hiện việc tuần tự hóa (serialization) các đối tượng, biến chúng thành một chuỗi byte để có thể ghi vào file hoặc gửi qua mạng.
    • ObjectOutputStream cần một luồng cơ bản (như FileOutputStream) để thực hiện ghi các byte đã tuần tự hóa vào file.

FileOutputStream làm nhiệm vụ mở một kết nối đến file cụ thể, trong khi ObjectOutputStream sử dụng kết nối này để ghi các đối tượng đã tuần tự hóa vào file.

Ví dụ minh họa:


Nếu bạn chỉ sử dụng ObjectOutputStream mà không có FileOutputStream, ObjectOutputStream sẽ không biết nơi để ghi các byte đã tuần tự hóa.

ObjectOutputStream không thể được sử dụng một mình vì nó là một luồng cao cấp (high-level stream) và cần một luồng cơ bản (low-level stream) để thực sự ghi các byte đã tuần tự hóa vào đâu đó, chẳng hạn như một file hoặc một kết nối mạng.

4.Phân loại theo loại luồng

4.1. Low-Level Streams (Luồng Cơ Bản)

Các luồng cơ bản đọc và ghi dữ liệu trực tiếp từ hoặc đến một nguồn như file, mảng byte, hoặc kết nối mạng. Chúng thực hiện các thao tác I/O ở mức byte hoặc ký tự đơn lẻ và không cung cấp tính năng bổ sung như bộ đệm hoặc tuần tự hóa.

Loại Luồng
Tên
Mục đích
Byte Streams (Luồng Byte)



InputStream
Lớp cha của tất cả các luồng byte đầu vào.

FileInputStream
Đọc byte từ file.

ByteArrayInputStream
Đọc byte từ mảng byte.

SocketInputStream
Đọc byte từ socket (kết nối mạng).

OutputStream
Lớp cha của tất cả các luồng byte đầu ra.

FileOutputStream
Ghi byte vào file.

ByteArrayOutputStream
Ghi byte vào mảng byte.

SocketOutputStream
Ghi byte vào socket (kết nối mạng).
Character Streams (Luồng Ký Tự)



Reader
Lớp cha của tất cả các luồng ký tự đầu vào.

FileReader
Đọc ký tự từ file.

CharArrayReader
Đọc ký tự từ mảng ký tự.

StringReader
Đọc ký tự từ chuỗi.

Writer
Lớp cha của tất cả các luồng ký tự đầu ra.

FileWriter
Ghi ký tự vào file.

CharArrayWriter
Ghi ký tự vào mảng ký tự.

StringWriter
Ghi ký tự vào chuỗi.

4.2.High-Level Streams (Luồng Cao Cấp)

Các luồng cao cấp được xây dựng trên các luồng cơ bản và cung cấp thêm các tính năng như bộ đệm (buffering), tuần tự hóa đối tượng (object serialization), hoặc xử lý dữ liệu dạng dòng (line processing).

Loại Luồng
Tên
Mục đích
Buffered Streams (Luồng Đệm)



BufferedInputStream
Thêm bộ đệm cho InputStream.

BufferedOutputStream
Thêm bộ đệm cho OutputStream.

BufferedReader
Thêm bộ đệm cho Reader và cung cấp phương thức đọc dòng.

BufferedWriter
Thêm bộ đệm cho Writer và cung cấp phương thức ghi dòng.
Object Streams (Luồng Đối Tượng)



ObjectInputStream
Đọc các đối tượng đã tuần tự hóa từ InputStream.

ObjectOutputStream
Ghi các đối tượng đã tuần tự hóa vào OutputStream.
Data Streams (Luồng Dữ Liệu)



DataInputStream
Đọc dữ liệu nguyên thủy (primitive data) từ InputStream.

DataOutputStream
Ghi dữ liệu nguyên thủy vào OutputStream.

B. Thao tác với file

1. Ghi nội dung vào file

Để ghi nội dung vào file, bạn có thể sử dụng lớp FileWriter. Dưới đây là ví dụ chi tiết:

  1. Tạo một đối tượng File: Kiểm tra xem file có tồn tại không, nếu không thì tạo một file mới.
  2. Tạo một đối tượng FileWriter: Sử dụng đối tượng này để ghi nội dung vào file.
  3. Viết nội dung vào file: Sử dụng phương thức write() của FileWriter.
  4. Đóng FileWriter: Đảm bảo rằng tài nguyên được giải phóng.

2. Đọc nội dung từ file

Để đọc nội dung từ file, bạn có thể sử dụng lớp FileReader kết hợp với BufferedReader để đọc dữ liệu từng dòng hoặc từng ký tự. Dưới đây là ví dụ chi tiết:

  1. Tạo một đối tượng File: Xác định file cần đọc.
  2. Tạo một đối tượng FileReaderBufferedReader: Sử dụng đối tượng này để đọc dữ liệu từ file.
  3. Đọc nội dung từ file: Sử dụng phương thức readLine() để đọc từng dòng.
  4. Đóng BufferedReader: Đảm bảo rằng tài nguyên được giải phóng.






m

Không có nhận xét nào:

Đăng nhận xét