CodeGym /Các khóa học /JAVA 25 SELF /Đọc và ghi tệp: thao tác cơ bản

Đọc và ghi tệp: thao tác cơ bản

JAVA 25 SELF
Mức độ , Bài học
Có sẵn

1. Đọc toàn bộ tệp

Đọc tất cả byte

Đôi khi cần trích xuất toàn bộ nội dung từ tệp một lần — nguyên trạng, không phân tích, dù đó là ảnh, kho lưu trữ hay một định dạng nhị phân nào đó. Để làm việc này trong Java có phương thức Files.readAllBytes(path). Nó trả về một mảng byte (byte[]), tức dữ liệu thô của tệp mà bạn có thể sử dụng theo bất kỳ cách nào.

import java.nio.file.*;

public class ReadBytesExample {
    public static void main(String[] args) throws Exception {
        Path path = Paths.get("example.txt");
        byte[] allBytes = Files.readAllBytes(path);
        System.out.println("Độ dài tệp (byte): " + allBytes.length);
    }
}

Ghi chú: Nếu tệp lớn (hàng gigabyte), cách tiếp cận này có thể gây vấn đề về bộ nhớ — vì toàn bộ nội dung tệp được nạp ngay vào RAM.

Đọc tất cả dòng

Với tệp văn bản có cách dễ đọc hơn: Files.readAllLines(path). Nó trả về danh sách dòng (List<String>), trong đó mỗi phần tử là một dòng từ tệp (các ký tự xuống dòng được xử lý tự động).

import java.nio.file.*;
import java.util.List;

public class ReadLinesExample {
    public static void main(String[] args) throws Exception {
        Path path = Paths.get("example.txt");
        List<String> lines = Files.readAllLines(path);

        for (String line : lines) {
            System.out.println(line);
        }
    }
}

Quan trọng: Mặc định sẽ dùng bộ mã của hệ thống. Nếu muốn chỉ định rõ bộ mã (ví dụ UTF-8), hãy dùng phiên bản nạp chồng của phương thức:

List<String> lines = Files.readAllLines(path, java.nio.charset.StandardCharsets.UTF_8);

Ví dụ: đọc tệp và in ra màn hình

Hãy thêm điều này vào ứng dụng nhỏ của chúng ta (ví dụ “notepad” hoặc “danh sách việc cần làm”):

import java.nio.file.*;
import java.util.List;

public class TodoReader {
    public static void main(String[] args) throws Exception {
        Path todoPath = Paths.get("todo.txt");
        if (Files.exists(todoPath)) {
            List<String> tasks = Files.readAllLines(todoPath);
            System.out.println("Các việc cần làm hôm nay của bạn:");
            for (String task : tasks) {
                System.out.println("- " + task);
            }
        } else {
            System.out.println("Không tìm thấy tệp todo.txt. Hãy tạo danh sách nhiệm vụ!");
        }
    }
}

2. Ghi vào tệp

Ghi mảng byte

Nếu bạn có một mảng byte (ví dụ kết quả tuần tự hóa, hình ảnh, âm thanh), hãy dùng Files.write(path, bytes). Phương thức này sẽ tạo tệp nếu chưa có hoặc ghi đè tệp hiện có.

import java.nio.file.*;

public class WriteBytesExample {
    public static void main(String[] args) throws Exception {
        Path path = Paths.get("output.bin");
        byte[] data = {1, 2, 3, 4, 5};
        Files.write(path, data);
        System.out.println("Đã ghi byte vào tệp output.bin");
    }
}

Ghi danh sách chuỗi

Với tệp văn bản, thuận tiện hơn khi dùng danh sách các chuỗi — mỗi chuỗi sẽ được ghi trên một dòng với ký tự xuống dòng.

import java.nio.file.*;
import java.util.Arrays;
import java.util.List;

public class WriteLinesExample {
    public static void main(String[] args) throws Exception {
        Path path = Paths.get("todo.txt");
        List<String> tasks = Arrays.asList(
            "Mua sữa",
            "Gọi điện cho bà",
            "Làm bài tập về nhà môn Java"
        );
        Files.write(path, tasks);
        System.out.println("Danh sách nhiệm vụ đã được ghi vào todo.txt");
    }
}

Lưu ý: Theo mặc định, tệp sẽ bị ghi đè! Nếu tệp đã tồn tại, các nhiệm vụ cũ sẽ biến mất. (Cách không làm mất dữ liệu cũ — chúng ta sẽ nói ngay sau.)

Ghi một chuỗi vào tệp

Nếu chỉ muốn ghi một chuỗi — hãy tạo danh sách chỉ có một phần tử hoặc dùng Files.write(path, string.getBytes()):

import java.nio.file.*;

public class WriteStringExample {
    public static void main(String[] args) throws Exception {
        Path path = Paths.get("hello.txt");
        String message = "Xin chào, Java!";
        Files.write(path, message.getBytes());
        System.out.println("Đã ghi chuỗi vào hello.txt");
    }
}

Chỉ định rõ bộ mã

Để tránh “ký tự rác” khi làm việc với văn bản (không chỉ tiếng Nga), hãy luôn chỉ định bộ mã:

import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.util.List;

public class WriteLinesUtf8Example {
    public static void main(String[] args) throws Exception {
        Path path = Paths.get("hello.txt");
        List<String> lines = List.of("Xin chào, thế giới!", "Đây là Java.");
        Files.write(path, lines, StandardCharsets.UTF_8);
    }
}

3. Xử lý lỗi

Khi làm việc với tệp, Java có thể ném ra ngoại lệ IOException. Đây là tín hiệu chung cho biết “đã có gì đó không ổn” ở tầng nhập/xuất. Có rất nhiều nguyên nhân: không có tệp, không đủ quyền truy cập, đĩa đầy hoặc đang bận, thậm chí có khi USB bị rút ra vào đúng lúc không thích hợp.

Vì vậy, các phương thức đọc và ghi tệp luôn yêu cầu chúng ta sẵn sàng xử lý các tình huống như vậy. Làm điều đó thông qua try-catch:

import java.nio.file.*;
import java.util.List;
import java.io.IOException;

public class SafeReadExample {
    public static void main(String[] args) {
        Path path = Paths.get("todo.txt");
        try {
            List<String> lines = Files.readAllLines(path);
            System.out.println("Nội dung tệp:");
            for (String line : lines) {
                System.out.println(line);
            }
        } catch (IOException ex) {
            System.out.println("Lỗi khi đọc tệp: " + ex.getMessage());
        }
    }
}

Tại sao cần làm vậy?

try-catch biến một cú sập tiềm ẩn của chương trình thành tình huống có kiểm soát. Nếu tệp không tồn tại — ta thông báo bình tĩnh cho người dùng. Nếu hệ thống không cho phép truy cập — ta chỉ ra vấn đề. Và nếu đĩa “biến mất” giữa chừng, chương trình sẽ không sập với một stack trace đáng sợ, mà ít nhất sẽ cảnh báo bằng một thông điệp bình thường. Nói cách khác: IOException không phải kẻ thù, mà là cách Java nói với ta: “này, có gì đó bên ngoài đang không ổn, hãy quyết định xem phải làm gì tiếp theo”.

4. Ví dụ thực tiễn

Đọc tệp và in nội dung

import java.nio.file.*;
import java.util.List;
import java.io.IOException;

public class PrintFileExample {
    public static void main(String[] args) {
        Path path = Paths.get("notes.txt");
        try {
            if (!Files.exists(path)) {
                System.out.println("Không tìm thấy tệp notes.txt.");
                return;
            }
            List<String> lines = Files.readAllLines(path, java.nio.charset.StandardCharsets.UTF_8);
            System.out.println("Nội dung tệp notes.txt:");
            for (String line : lines) {
                System.out.println(line);
            }
        } catch (IOException e) {
            System.out.println("Lỗi khi đọc tệp: " + e.getMessage());
        }
    }
}

Ghi một chuỗi vào tệp mới

import java.nio.file.*;
import java.nio.charset.StandardCharsets;
import java.io.IOException;

public class WriteFileExample {
    public static void main(String[] args) {
        Path path = Paths.get("greeting.txt");
        String content = "Chào mừng đến với thế giới Java IO!";
        try {
            Files.write(path, content.getBytes(StandardCharsets.UTF_8));
            System.out.println("Đã ghi chuỗi vào tệp greeting.txt");
        } catch (IOException e) {
            System.out.println("Lỗi khi ghi tệp: " + e.getMessage());
        }
    }
}

Ghi danh sách chuỗi vào tệp

import java.nio.file.*;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.io.IOException;

public class WriteListExample {
    public static void main(String[] args) {
        Path path = Paths.get("shopping.txt");
        List<String> items = List.of("Bánh mì", "Sữa", "Phô mai");
        try {
            Files.write(path, items, StandardCharsets.UTF_8);
            System.out.println("Danh sách mua sắm đã được ghi vào shopping.txt");
        } catch (IOException e) {
            System.out.println("Lỗi khi ghi tệp: " + e.getMessage());
        }
    }
}

5. Ngắn gọn về stream

Các phương thức Files.readAllBytes, Files.readAllLines, Files.write là kiểu “mọi thứ một lần”: hoặc toàn bộ tệp vào bộ nhớ, hoặc toàn bộ nội dung ra đĩa ngay lập tức. Với tệp nhỏ thì tiện, còn tệp lớn — không hay lắm (có thể gặp OutOfMemoryError hoặc “đứng” khi đọc log hàng gigabyte).

Để làm việc với tệp lớn hoặc đọc theo từng dòng hãy dùng stream:

  • Văn bản: BufferedReader, BufferedWriter
  • Byte: InputStream, OutputStream

Chúng ta sẽ nói chi tiết về stream trong các bài giảng tiếp theo, dưới đây là một đoạn giới thiệu nhỏ:

import java.nio.file.*;
import java.io.*;

public class BufferedReaderExample {
    public static void main(String[] args) {
        Path path = Paths.get("bigfile.txt");
        try (BufferedReader reader = Files.newBufferedReader(path)) {
            String line;
            while ((line = reader.readLine()) != null) {
                // Xử lý dòng
                System.out.println(line);
            }
        } catch (IOException e) {
            System.out.println("Lỗi khi đọc tệp: " + e.getMessage());
        }
    }
}

6. Những điểm hữu ích

  • Ghi đè tệp: Tất cả các phương thức Files.write mặc định sẽ ghi đè tệp. Nếu cần thêm vào cuối, hãy sử dụng các tùy chọn bổ sung (về việc này — trong bài giảng sau).
  • Bộ mã: Luôn chỉ định bộ mã một cách rõ ràng khi làm việc với văn bản không phải ASCII. Điều này sẽ tránh “ký tự rác” ở đầu ra. Lựa chọn tốt — StandardCharsets.UTF_8.
  • Đường dẫn: Dùng Paths.get(...) để đảm bảo đa nền tảng. Không bao giờ hard-code dấu gạch chéo (/ hoặc \) thủ công.
  • Tên tệp: Không sử dụng các ký tự bị cấm trong tên tệp (?, *, :, <, >, |, v.v.), đặc biệt nếu mã của bạn phải chạy trên Windows.

7. Các lỗi thường gặp khi đọc và ghi tệp

Lỗi số 1: Không xử lý IOException. Người mới thường viết Files.readAllLines(path) mà không có try-catch, và ngay khi có vấn đề (tệp không tồn tại, thiếu quyền, đĩa hỏng) chương trình sẽ sập. Luôn xử lý ngoại lệ!

Lỗi số 2: Làm việc với tệp lớn qua readAllBytes/readAllLines. Nếu tệp nặng hàng trăm MB hoặc GB, cố gắng nạp toàn bộ sẽ có thể “giết” chương trình của bạn. Với những trường hợp này hãy dùng stream (BufferedReader).

Lỗi số 3: Không chỉ định bộ mã khi ghi/đọc văn bản. Nếu không chỉ định bộ mã, kết quả có thể khác nhau trên các máy và hệ điều hành khác nhau. Vấn đề này xuất hiện rất thường xuyên với các bảng chữ như Cyrillic. Hãy dùng StandardCharsets.UTF_8 hoặc bộ mã bạn cần một cách rõ ràng.

Lỗi số 4: Nhầm tưởng File/Path chính là tệp. Lớp File hoặc Path chỉ là “nhãn” trỏ tới tệp, không phải bản thân tệp. Tạo đối tượng không có nghĩa là tạo tệp trên đĩa. Để tạo tệp — hãy dùng các phương thức Files.createFile, Files.write, v.v.

Lỗi số 5: Không đóng stream (nếu bạn dùng stream). Nếu bạn dùng stream thủ công (ví dụ BufferedReader), nhất thiết phải đóng chúng (tốt nhất là qua try-with-resources). Nếu không, tệp có thể bị “khóa” và không truy cập được từ chương trình khác.

Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION