java-recipes

ホーム ファイルI/O › F-03b: java.nio.file ファイル操作

F-03b: java.nio.file によるファイル操作

Java 7 で導入された NIO.2(java.nio.file)パッケージは、 従来の java.io.File クラスより直感的で強力なファイル操作を提供します。 現代の Java 開発では NIO.2 の Files クラスを使うのが一般的です。

なぜ NIO.2 を使うのか

旧来の java.io.File クラスには以下の問題がありました。

  • メソッドの戻り値が boolean のみで、失敗した理由がわからない
  • 例外を投げないため、エラーが発生しても無言で失敗する
  • シンボリックリンクや属性へのアクセスができない

NIO.2 の Files クラスは失敗時に明示的な例外を投げるため、 エラーハンドリングがしやすく、コードの意図も明確になります。

主要な API(Java バージョン別)

API追加バージョン説明
Files.write / readAllLines / readAllBytes / copy / move / deleteJava 7+基本的なファイル操作
Files.list / walkJava 8+ディレクトリ列挙(Stream を返す)
Files.writeString / readStringJava 11+文字列をそのまま読み書き
Files.mismatchJava 12+2ファイルの比較
Path.of()Java 11+Paths.get() の簡略版

サンプルコード

Sample.java
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Arrays;
import java.util.List;

public class NioFileSample {

    public static void main(String[] args) throws IOException {

        // ① ファイルに書き込む(List<String> を UTF-8 で書き込み)
        Path file = Paths.get("sample_nio.txt");
        List<String> lines = Arrays.asList("1行目:java.nio.file サンプル", "2行目:NIO.2 API", "3行目:try-with-resources");
        Files.write(file, lines, StandardCharsets.UTF_8);
        System.out.println("書き込み完了: " + file.toAbsolutePath());

        // ② ファイルを読み込む(全行をリストで取得)
        List<String> readLines = Files.readAllLines(file, StandardCharsets.UTF_8);
        for (String line : readLines) {
            System.out.println("読み込み: " + line);
        }

        // ③ バイト配列で読み込む(バイナリファイルにも使える)
        byte[] bytes = Files.readAllBytes(file);
        System.out.println("バイト数: " + bytes.length);

        // ④ ディレクトリを再帰的に作成
        Path dir = Paths.get("work_nio/sub");
        Files.createDirectories(dir);
        System.out.println("ディレクトリ作成: " + dir.toAbsolutePath());

        // ⑤ ファイルをコピー(上書き許可)
        Path copy = dir.resolve("sample_copy.txt");
        Files.copy(file, copy, StandardCopyOption.REPLACE_EXISTING);
        System.out.println("コピー完了: " + copy);

        // ⑥ ファイルを移動(リネームにも使える)
        Path moved = dir.resolve("sample_moved.txt");
        Files.move(copy, moved, StandardCopyOption.REPLACE_EXISTING);
        System.out.println("移動完了: " + moved);

        // ⑦ ファイルの存在確認・属性取得
        System.out.println("存在確認: " + Files.exists(moved));
        System.out.println("ファイルサイズ: " + Files.size(moved) + " bytes");
        BasicFileAttributes attrs = Files.readAttributes(moved, BasicFileAttributes.class);
        System.out.println("最終更新時刻: " + attrs.lastModifiedTime());

        // ⑧ ディレクトリ内のエントリを列挙(DirectoryStream で try-with-resources)
        System.out.println("--- ディレクトリ内容 ---");
        try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
            for (Path entry : stream) {
                System.out.println("  " + entry.getFileName());
            }
        }

        // ⑨ 後片付け(削除)
        Files.delete(moved);
        Files.delete(dir);
        Files.delete(Paths.get("work_nio"));
        Files.delete(file);
        System.out.println("削除完了");
    }
}

よくあるミス・注意点

⚠️ Files.list() / Files.walk() は必ず try-with-resources で閉じる

Files.list() Files.walk() Stream<Path> を返しますが、 内部でファイルディスクリプタを保持しているため、必ず try-with-resources で閉じる必要があります。 閉じないとファイルディスクリプタが枯渇する可能性があります。

// NG: close されない
Stream<Path> stream = Files.list(dir);
stream.forEach(System.out::println);

// OK: try-with-resources で確実に close
try (Stream<Path> stream = Files.list(dir)) {
    stream.forEach(System.out::println);
}

📌 Files.delete() は空のディレクトリしか削除できない

Files.delete() は空でないディレクトリを渡すと DirectoryNotEmptyException をスローします。 ディレクトリを再帰削除したい場合は Files.walk() で降順に処理するか、 Apache Commons IO などの外部ライブラリを使います。

📌 java.io.File と java.nio.file.Path は相互変換できる

古い API と新しい API を混在させる必要がある場合は変換して対応できます。

File file = new File("example.txt");
Path path = file.toPath();    // File → Path

Path path2 = Path.of("example.txt");
File file2 = path2.toFile();  // Path → File

テストする観点

  • 存在しないファイルを読もうとすると NoSuchFileException がスローされるか
  • 存在しない中間ディレクトリに Files.write() すると例外になるか(createDirectories() が先に必要)
  • 空でないディレクトリを Files.delete() すると DirectoryNotEmptyException が発生するか
  • 書き込んだ内容をそのまま読み返すと一致するか(ラウンドトリップテスト)
  • UTF-8 の日本語を含むファイルが正しく読み書きできるか
  • Files.copy()REPLACE_EXISTING を指定しない場合、既存ファイルがあると例外になるか

GitHub でソースコードを見る →