Java 檔案操作

Java 提供多種方式操作檔案和目錄,包括傳統的 java.io.File 和較新的 java.nio.file API。

File 類別

java.io.File 是操作檔案和目錄的基本類別:

import java.io.File;

// 建立 File 物件(不會真的建立檔案)
File file = new File("test.txt");
File file2 = new File("/Users/user/documents/test.txt");
File file3 = new File("documents", "test.txt");

基本操作

檢查檔案/目錄

File file = new File("test.txt");

// 檢查存在
boolean exists = file.exists();

// 檢查類型
boolean isFile = file.isFile();
boolean isDir = file.isDirectory();

// 檢查權限
boolean canRead = file.canRead();
boolean canWrite = file.canWrite();
boolean canExecute = file.canExecute();

// 檢查是否隱藏
boolean hidden = file.isHidden();

取得檔案資訊

File file = new File("test.txt");

// 檔案名稱
String name = file.getName();            // "test.txt"

// 路徑
String path = file.getPath();            // 相對或絕對路徑
String absPath = file.getAbsolutePath(); // 絕對路徑
String canPath = file.getCanonicalPath();// 標準化路徑

// 父目錄
String parent = file.getParent();
File parentFile = file.getParentFile();

// 檔案大小(位元組)
long size = file.length();

// 最後修改時間
long lastModified = file.lastModified();

建立檔案和目錄

// 建立新檔案
File file = new File("newfile.txt");
boolean created = file.createNewFile();  // 如果不存在則建立

// 建立目錄
File dir = new File("newdir");
boolean dirCreated = dir.mkdir();        // 建立單層目錄

// 建立多層目錄
File dirs = new File("path/to/newdir");
boolean dirsCreated = dirs.mkdirs();     // 建立所有必要的父目錄

刪除和重新命名

// 刪除
File file = new File("test.txt");
boolean deleted = file.delete();

// 在 JVM 結束時刪除
file.deleteOnExit();

// 重新命名/移動
File oldFile = new File("old.txt");
File newFile = new File("new.txt");
boolean renamed = oldFile.renameTo(newFile);

列出目錄內容

File dir = new File(".");

// 列出檔案名稱
String[] names = dir.list();

// 列出 File 物件
File[] files = dir.listFiles();

// 使用過濾器
File[] txtFiles = dir.listFiles((d, name) -> name.endsWith(".txt"));
File[] dirs = dir.listFiles(File::isDirectory);

// 遍歷
for (File f : dir.listFiles()) {
    System.out.println(f.getName() + " - " + 
                      (f.isDirectory() ? "目錄" : "檔案"));
}

NIO.2 Path 和 Files

Java 7 引入的新 API,功能更強大:

import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.Files;

// 建立 Path
Path path = Paths.get("test.txt");
Path path2 = Paths.get("/Users", "user", "documents", "test.txt");
Path path3 = Path.of("test.txt");  // Java 11+

檢查和取得資訊

Path path = Paths.get("test.txt");

// 檢查
boolean exists = Files.exists(path);
boolean isFile = Files.isRegularFile(path);
boolean isDir = Files.isDirectory(path);
boolean isReadable = Files.isReadable(path);
boolean isWritable = Files.isWritable(path);

// 檔案大小
long size = Files.size(path);

// 最後修改時間
FileTime lastModified = Files.getLastModifiedTime(path);

建立檔案和目錄

// 建立檔案
Path file = Files.createFile(Paths.get("newfile.txt"));

// 建立目錄
Path dir = Files.createDirectory(Paths.get("newdir"));

// 建立多層目錄
Path dirs = Files.createDirectories(Paths.get("path/to/newdir"));

// 建立暫存檔案
Path tempFile = Files.createTempFile("prefix", ".txt");
Path tempDir = Files.createTempDirectory("tempdir");

複製和移動

Path source = Paths.get("source.txt");
Path target = Paths.get("target.txt");

// 複製
Files.copy(source, target);
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);

// 移動
Files.move(source, target);
Files.move(source, target, StandardCopyOption.REPLACE_EXISTING);

刪除

Path path = Paths.get("test.txt");

// 刪除(如果不存在會拋出例外)
Files.delete(path);

// 如果存在才刪除
boolean deleted = Files.deleteIfExists(path);

遍歷目錄

Path dir = Paths.get(".");

// 列出目錄內容
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
    for (Path entry : stream) {
        System.out.println(entry.getFileName());
    }
}

// 使用 glob 模式過濾
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, "*.txt")) {
    for (Path entry : stream) {
        System.out.println(entry);
    }
}

// 遞迴遍歷
Files.walk(dir)
     .filter(Files::isRegularFile)
     .forEach(System.out::println);

// 限制深度
Files.walk(dir, 2)
     .forEach(System.out::println);

實際範例

取得目錄大小

public long getDirectorySize(Path dir) throws IOException {
    return Files.walk(dir)
                .filter(Files::isRegularFile)
                .mapToLong(p -> {
                    try {
                        return Files.size(p);
                    } catch (IOException e) {
                        return 0;
                    }
                })
                .sum();
}

尋找檔案

public List<Path> findFiles(Path dir, String extension) throws IOException {
    return Files.walk(dir)
                .filter(Files::isRegularFile)
                .filter(p -> p.toString().endsWith(extension))
                .collect(Collectors.toList());
}

// 使用
List<Path> javaFiles = findFiles(Paths.get("."), ".java");

複製目錄

public void copyDirectory(Path source, Path target) throws IOException {
    Files.walk(source).forEach(s -> {
        try {
            Path t = target.resolve(source.relativize(s));
            if (Files.isDirectory(s)) {
                Files.createDirectories(t);
            } else {
                Files.copy(s, t, StandardCopyOption.REPLACE_EXISTING);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    });
}

刪除目錄(包含內容)

public void deleteDirectory(Path dir) throws IOException {
    Files.walk(dir)
         .sorted(Comparator.reverseOrder())  // 先刪除檔案,再刪除目錄
         .forEach(p -> {
             try {
                 Files.delete(p);
             } catch (IOException e) {
                 throw new RuntimeException(e);
             }
         });
}

監控目錄變化

public void watchDirectory(Path dir) throws IOException, InterruptedException {
    WatchService watchService = FileSystems.getDefault().newWatchService();

    dir.register(watchService,
        StandardWatchEventKinds.ENTRY_CREATE,
        StandardWatchEventKinds.ENTRY_DELETE,
        StandardWatchEventKinds.ENTRY_MODIFY);

    while (true) {
        WatchKey key = watchService.take();
        for (WatchEvent<?> event : key.pollEvents()) {
            WatchEvent.Kind<?> kind = event.kind();
            Path filename = (Path) event.context();
            System.out.println(kind + ": " + filename);
        }
        key.reset();
    }
}

File vs Path

特性FilePath
套件java.iojava.nio.file
版本Java 1.0Java 7
不可變性可變不可變
例外處理回傳 boolean拋出例外
功能基本豐富
建議舊程式碼新程式碼
// 轉換
File file = new File("test.txt");
Path path = file.toPath();

Path path2 = Paths.get("test.txt");
File file2 = path2.toFile();

重點整理

  • File 是傳統 API,Path/Files 是較新的 NIO.2 API
  • 新程式碼建議使用 PathFiles
  • Files.walk() 方便遞迴遍歷目錄
  • 注意關閉 Stream 和 DirectoryStream(使用 try-with-resources)
  • WatchService 可以監控目錄變化