Java NIO 檔案操作

Java NIO (New I/O) 提供更現代化的檔案操作 API,主要透過 java.nio.file 套件中的 FilesPath 類別。

引入套件

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

Path 路徑

// 建立 Path
Path path1 = Paths.get("file.txt");
Path path2 = Paths.get("/Users", "name", "file.txt");
Path path3 = Path.of("file.txt");  // Java 11+

// 路徑操作
Path absolute = path1.toAbsolutePath();
Path parent = path1.getParent();
Path fileName = path1.getFileName();
Path resolved = path1.resolve("subdir/file.txt");
Path normalized = Paths.get("a/b/../c").normalize();  // a/c

讀取檔案

讀取全部內容

// 讀取為字串(Java 11+)
String content = Files.readString(Path.of("file.txt"));

// 讀取為字串(指定編碼)
String content2 = Files.readString(Path.of("file.txt"), StandardCharsets.UTF_8);

// 讀取所有行
List<String> lines = Files.readAllLines(Path.of("file.txt"));

// 讀取為位元組
byte[] bytes = Files.readAllBytes(Path.of("file.bin"));

Stream 方式讀取

// 逐行處理(適合大檔案)
try (Stream<String> lines = Files.lines(Path.of("large.txt"))) {
    lines.filter(line -> line.contains("ERROR"))
         .forEach(System.out::println);
}

// 統計行數
long count = Files.lines(Path.of("file.txt")).count();

寫入檔案

// 寫入字串(Java 11+)
Files.writeString(Path.of("file.txt"), "Hello, World!");

// 寫入字串(指定選項)
Files.writeString(Path.of("file.txt"), "附加內容",
    StandardOpenOption.APPEND, StandardOpenOption.CREATE);

// 寫入多行
List<String> lines = Arrays.asList("Line 1", "Line 2", "Line 3");
Files.write(Path.of("file.txt"), lines);

// 寫入位元組
byte[] data = {0x48, 0x65, 0x6C, 0x6C, 0x6F};
Files.write(Path.of("file.bin"), data);

檔案操作

Path source = Path.of("source.txt");
Path target = Path.of("target.txt");

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

// 移動/重新命名
Files.move(source, target);
Files.move(source, target, StandardCopyOption.ATOMIC_MOVE);

// 刪除
Files.delete(Path.of("file.txt"));
Files.deleteIfExists(Path.of("file.txt"));

// 建立檔案和目錄
Files.createFile(Path.of("new.txt"));
Files.createDirectory(Path.of("newdir"));
Files.createDirectories(Path.of("a/b/c"));  // 建立多層目錄

檔案資訊

Path path = Path.of("file.txt");

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

// 屬性
long size = Files.size(path);
FileTime modified = Files.getLastModifiedTime(path);

// 設定修改時間
Files.setLastModifiedTime(path, FileTime.fromMillis(System.currentTimeMillis()));

目錄遍歷

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

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

// 遞迴遍歷
try (Stream<Path> paths = Files.walk(Path.of("."))) {
    paths.filter(Files::isRegularFile)
         .forEach(System.out::println);
}

// 限制深度
try (Stream<Path> paths = Files.walk(Path.of("."), 2)) {
    paths.forEach(System.out::println);
}

// 尋找檔案
try (Stream<Path> paths = Files.find(Path.of("."), 10,
        (path, attrs) -> path.toString().endsWith(".java"))) {
    paths.forEach(System.out::println);
}

暫存檔案

// 建立暫存檔案
Path tempFile = Files.createTempFile("prefix", ".txt");
System.out.println(tempFile);  // /tmp/prefix12345.txt

// 建立暫存目錄
Path tempDir = Files.createTempDirectory("myapp");

// 使用完後刪除
tempFile.toFile().deleteOnExit();

實用範例

搜尋檔案內容

public static List<Path> searchInFiles(Path dir, String keyword) throws IOException {
    try (Stream<Path> paths = Files.walk(dir)) {
        return paths
            .filter(Files::isRegularFile)
            .filter(p -> p.toString().endsWith(".txt"))
            .filter(p -> {
                try {
                    return Files.readString(p).contains(keyword);
                } catch (IOException e) {
                    return false;
                }
            })
            .collect(Collectors.toList());
    }
}

計算目錄大小

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

重點整理

  • NIO 使用 Path 表示路徑,Files 進行操作
  • Files.readString() / writeString() 簡化文字檔操作
  • Files.lines() 適合大檔案的 Stream 處理
  • Files.walk() 遞迴遍歷目錄
  • 提供完整的檔案屬性和權限操作
  • 比傳統 I/O 更現代化且功能更強