CodeGym /Các khóa học /JAVA 25 SELF /Kiểm tra sự tồn tại của tệp và thư mục

Kiểm tra sự tồn tại của tệp và thư mục

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

1. Phương thức kiểm tra sự tồn tại của tệp và thư mục

Làm việc với tệp đôi khi như đi dạo trong bãi mìn: không bao giờ biết điều gì chờ bạn sau byte tiếp theo. Tin vui: Java cho chúng ta “máy dò kim loại” – các phương thức để kiểm tra sự tồn tại của tệp và thư mục.

Lớp File: kiểm tra bằng exists(), isFile(), isDirectory()

Cách cổ điển – sử dụng lớp java.io.File:

File file = new File("example.txt");
if (file.exists()) {
    System.out.println("Tệp tồn tại!");
} else {
    System.out.println("Không tìm thấy tệp.");
}

Phương thức exists() trả về true nếu tệp hoặc thư mục với tên đó tồn tại. Nhưng chưa hết! Đôi khi bạn cần biết chính xác cái gì tồn tại: tệp hay thư mục.

if (file.isFile()) {
    System.out.println("Đây là tệp.");
} else if (file.isDirectory()) {
    System.out.println("Đây là thư mục.");
} else {
    System.out.println("Không tìm thấy gì.");
}

Các lớp Path và Files: cách tiếp cận hiện đại

Trong các chương trình hiện đại, tốt hơn nên dùng API mới hơn và mạnh mẽ hơn java.nio.file. Ở đây, để kiểm tra sự tồn tại, dùng phương thức tĩnh Files.exists():

import java.nio.file.*;

Path path = Paths.get("example.txt");
if (Files.exists(path)) {
    System.out.println("Tệp được tìm thấy qua NIO!");
}

Để kiểm tra kiểu đối tượng, hãy dùng:

if (Files.isRegularFile(path)) {
    System.out.println("Đây là tệp thông thường!");
}
if (Files.isDirectory(path)) {
    System.out.println("Đây là thư mục!");
}

Gợi ý: với các dự án mới, tốt nhất dùng ngay NIO (Path, Files), vì API này hiện đại hơn, hỗ trợ nhiều tính năng hơn và làm việc rất tốt với try-with-resources.

Bảng: so sánh các cách tiếp cận

Cách Kiểm tra tồn tại Kiểm tra kiểu (tệp/thư mục) Tính hiện đại
File.exists()
Có (isFile(), isDirectory())
Files.exists(Path)
Có (isRegularFile(), isDirectory()) Khuyến nghị

2. Vấn đề TOCTOU: vì sao kiểm tra không phải là “thuốc chữa bách bệnh”

Vấn đề là gì?

Giả sử bạn đã kiểm tra: “Tệp tồn tại!” – và ngay lập tức quyết định đọc nó. Nhưng giữa hai thao tác này có thể là cả “vĩnh hằng” theo thang đo của CPU. Trong thời gian đó, tệp có thể bị xóa, di chuyển, thay thế bởi tiến trình khác hoặc quyền truy cập của nó có thể thay đổi.

Giống như bạn mở tủ lạnh, thấy có bánh, đóng cửa lại, rồi mở ra lần nữa – và phát hiện chiếc bánh đã bị ăn mất. Trong lập trình cũng có những “người nhà” như vậy: các tiến trình khác, người dùng khác, phần mềm diệt virus, chính hệ thống tệp.

Tại sao điều này quan trọng?

Kết quả là, ngay cả khi bạn đã kiểm tra rằng tệp tồn tại, khi cố gắng mở nó, vẫn có thể phát sinh ngoại lệ — ví dụ, FileNotFoundException hoặc AccessDeniedException.

Vì thế, kiểm tra không phải là đảm bảo, mà chỉ là biện pháp hỗ trợ thêm. Luôn sẵn sàng cho ngoại lệ và hãy xử lý chúng!

3. Thực hành: kiểm tra sự tồn tại của tệp trước khi đọc

Thêm một hàm hiển thị nội dung tệp nếu nó tồn tại và thông báo cho người dùng nếu không có tệp. Sẽ có hai cách: qua API cũ và API mới.

Phương án 1: qua File

import java.io.*;

public class FileExistenceCheck {
    public static void main(String[] args) {
        File file = new File("notes.txt");
        if (file.exists() && file.isFile()) {
            try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    System.out.println(line);
                }
            } catch (IOException e) {
                System.out.println("Lỗi khi đọc tệp: " + e.getMessage());
            }
        } else {
            System.out.println("Không tìm thấy tệp 'notes.txt'.");
        }
    }
}

Phương án 2: qua PathFiles

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

public class PathExistenceCheck {
    public static void main(String[] args) {
        Path path = Paths.get("notes.txt");
        if (Files.exists(path) && Files.isRegularFile(path)) {
            try {
                Files.lines(path).forEach(System.out::println);
            } catch (IOException e) {
                System.out.println("Lỗi khi đọc tệp: " + e.getMessage());
            }
        } else {
            System.out.println("Không tìm thấy tệp 'notes.txt'.");
        }
    }
}

Ngay cả sau khi kiểm tra – vẫn phải bắt ngoại lệ!

Trong cả hai ví dụ, dù đã kiểm tra trước, chúng ta vẫn dùng các khối trycatch, vì tệp có thể biến mất hoặc trở nên không thể truy cập bất cứ lúc nào. Đây là “quy tắc vàng” khi làm việc với tệp!

4. Kiểm tra sự tồn tại của thư mục

Tương tự, bạn có thể kiểm tra thư mục có tồn tại hay không, và nếu không có — hãy tạo nó:

import java.nio.file.*;

public class DirectoryCheck {
    public static void main(String[] args) {
        Path dir = Paths.get("data");
        if (Files.exists(dir) && Files.isDirectory(dir)) {
            System.out.println("Đã tìm thấy thư mục 'data'.");
        } else {
            System.out.println("Không tìm thấy thư mục 'data'. Đang tạo...");
            try {
                Files.createDirectory(dir);
                System.out.println("Đã tạo thư mục!");
            } catch (IOException e) {
                System.out.println("Lỗi khi tạo thư mục: " + e.getMessage());
            }
        }
    }
}

Nhân tiện:
Phương thức Files.createDirectory() ném ngoại lệ nếu thư mục đã tồn tại. Nếu bạn muốn tạo một chuỗi thư mục (ví dụ "data/2025/09"), hãy dùng Files.createDirectories(), phương thức này không phàn nàn nếu một phần thư mục đã tồn tại.

5. Lưu ý và khác biệt: đường dẫn tương đối và tuyệt đối

Đường dẫn tương đối

Khi bạn viết "notes.txt", chương trình sẽ tìm tệp trong “thư mục làm việc hiện tại”. Thư mục này ở đâu phụ thuộc vào cách và từ đâu bạn chạy ứng dụng (IDE, terminal, nhấp đúp vào JAR, v.v.).

Đường dẫn tuyệt đối

Nếu cần chắc chắn nơi tìm kiếm, tốt hơn hãy dùng đường dẫn tuyệt đối hoặc xây dựng chúng một cách động:

String userHome = System.getProperty("user.home");
Path filePath = Paths.get(userHome, "myapp", "notes.txt");

Kiểm tra kiểu đối tượng

Đôi khi “tệp” có thể bất ngờ hóa ra là thư mục. Vì vậy hãy kiểm tra không chỉ sự tồn tại mà còn cả kiểu:

if (Files.isRegularFile(path)) {
    // Đây chính là tệp!
}
if (Files.isDirectory(path)) {
    // Đây là thư mục!
}

6. Minh họa vấn đề TOCTOU trong thực tế

Mô phỏng tình huống khi tệp biến mất sau khi kiểm tra nhưng trước khi mở. Hãy chạy mã và xóa tệp thủ công giữa bước kiểm tra và đọc:

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

public class TOCTOUExample {
    public static void main(String[] args) {
        Path path = Paths.get("notes.txt");
        if (Files.exists(path)) {
            System.out.println("Đã tìm thấy tệp, chuẩn bị đọc...");
            // Tại đây hãy mở trình quản lý tệp và xóa thủ công tệp "notes.txt"!
            try {
                Files.lines(path).forEach(System.out::println);
            } catch (IOException e) {
                System.out.println("Ôi! Tệp đã biến mất: " + e.getMessage());
            }
        } else {
            System.out.println("Không tìm thấy tệp.");
        }
    }
}

Kết quả:
Nếu bạn kịp xóa tệp giữa bước kiểm tra và bước đọc, bạn sẽ nhận ngoại lệ. Điều này cho thấy rõ rằng ngay cả kiểm tra cẩn thận cũng không cứu khỏi các thay đổi bất ngờ.

7. Những lỗi thường gặp khi kiểm tra sự tồn tại của tệp và thư mục

Lỗi số 1: Chỉ dựa vào kiểm tra, không dùng try-catch. “Tệp có rồi” nghĩa là có thể đọc thoải mái – người mới hay nghĩ vậy. Nhưng tệp có thể biến mất hoặc trở nên không thể truy cập và chương trình sẽ “đổ vỡ”.

Lỗi số 2: Chỉ kiểm tra sự tồn tại, không kiểm tra kiểu. Nếu bạn chỉ kiểm tra exists() mà không xác định rõ đó là tệp hay thư mục, bạn có thể thử mở một thư mục như tệp – và nhận lỗi.

Lỗi số 3: Dùng đường dẫn tương đối mà không hiểu thư mục làm việc. Chương trình tìm tệp “ở đâu đó không đúng”, và người dùng không hiểu vì sao không hoạt động.

Lỗi số 4: Không tính đến quyền truy cập. Tệp hoặc thư mục có thể tồn tại nhưng không có quyền đọc/ghi. Các lỗi như vậy chỉ xuất hiện khi cố mở tệp – hãy luôn dùng trycatch.

Lỗi số 5: Không để ý đến sự phân biệt chữ hoa/thường trong tên tệp trên các HĐH khác nhau. Trên Windows, tên tệp không phân biệt hoa/thường, còn trên Linux thì có. Chương trình có thể không tìm thấy "Notes.txt" nếu nó tìm "notes.txt".

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