Java NIO.2で“本気のファイル操作”に触ってみます

こんにちは、さるまりんです。

前に「Javaでテキストファイルを読むだけ」の記事を書いたのですが(懐かしい〜)、今回はその続編です。

今回は NIO.2(New I/O 2) を触ってみました。
Java 7 で登場したわりには、いまだに “知らないと損する便利API” みたいな扱いを受けている領域です。

ざっくり言うと:

  • Path … パスを“文字列じゃなくてオブジェクト”として扱える
  • Files … 「コピー」「移動」「読み書き」「探索」などを一発で
  • WatchService … フォルダの変更を“見張れる”
  • 安全な I/O の書き方が揃っている

File API より直感的で、OS差も吸収してくれるので、覚えるとかなり便利!

今回の記事では、まず “ちょっと本気寄りのファイル操作” を 5 つ、ミニ実験してみました。

1. SafeWrite — 一時ファイル → アトミック移動で安全に書く

「書きかけのファイルが壊れたら困る!」
そんなときは テンポラリに書いてから、本番ファイルへ一瞬で差し替えます。

// SafeWrite.java
import java.nio.file.*;

public class SafeWrite {
    public static void main(String[] args) throws Exception {
        Path temp = Paths.get("tmp-data.txt");
        Path real = Paths.get("data.txt");

        Files.writeString(temp, "Hello from safe write!\n");
        Files.move(temp, real, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);

        System.out.println("safe write done -> " + real.toAbsolutePath());
    }
}

ポイント:

  • ATOMIC_MOVE が “一瞬で入れ替え” の主役
  • ログ・設定ファイル更新・バッチ出力など、壊れてほしくない場面で本領発揮

2. WalkAndMatch — ディレクトリを歩きながらパターン絞り込み

Files.walk() でディレクトリを再帰で歩きつつ、
PathMatcherglob正規表現 の両方を使ってパターンマッチできます。

// WalkAndMatch.java
import java.nio.file.*;
import java.io.IOException;
import java.util.stream.Stream;

public class WalkAndMatch {
    public static void main(String[] args) throws IOException {
        Path root = Paths.get("data");

        PathMatcher glob = FileSystems.getDefault().getPathMatcher("glob:**/*.log");
        PathMatcher regex = FileSystems.getDefault().getPathMatcher("regex:.*/app-\\d{4}-\\d{2}-\\d{2}\\.log");

        try (Stream<Path> s = Files.walk(root)) {
            s.filter(Files::isRegularFile)
             .filter(p -> glob.matches(p) || regex.matches(p))
             .forEach(System.out::println);
        }
    }
}

ポイント:

  • "glob:**/*.log" で “どこにあるログでも拾う”
  • "regex:..." を使えば複雑なパターンにも対応
  • 再帰探索も勝手にやってくれるので、手書きの再帰とはお別れです

3. WatchDir — フォルダの“変化”をリアルタイム監視

WatchService を使うと、
ファイルの追加・更新・削除をリアルタイムで監視できます。

// WatchDir.java
import java.nio.file.*;

public class WatchDir {
    public static void main(String[] args) throws Exception {
        Path dir = Paths.get("data");
        WatchService watcher = FileSystems.getDefault().newWatchService();

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

        System.out.println("watching: " + dir.toAbsolutePath());
        for (;;) {
            WatchKey key = watcher.take();
            for (WatchEvent<?> ev : key.pollEvents()) {
                System.out.println("event: " + ev.kind() + " -> " + ev.context());
            }
            key.reset();
        }
    }
}

ポイント:

  • Mac / Linux / Windows 共通で動く
  • ログディレクトリを覗いたり、ファイルの到着待ちなどに使える
  • 一度動かしてみると“めちゃ楽しい”系API

4. CopyAttrs — 属性や権限を保ったままコピー

ただ内容をコピーするだけでなく、
パーミッション・タイムスタンプも維持したままコピーできます。

// CopyAttrs.java
import java.nio.file.*;

public class CopyAttrs {
    public static void main(String[] args) throws Exception {
        Path src = Paths.get("sample.txt");
        Path dst = Paths.get("copied.txt");

        Files.copy(src, dst,
            StandardCopyOption.COPY_ATTRIBUTES,
            StandardCopyOption.REPLACE_EXISTING
        );

        System.out.println("copied with attrs -> " + dst.toAbsolutePath());
    }
}

ポイント:

  • バックアップ・世代管理などで便利
  • COPY_ATTRIBUTES を足すだけでOK

5. LineStream — 行単位ストリーム処理のおまけ

行ストリームで “読みながら加工して出力” できます。

// LineStream.java
import java.nio.file.*;
import java.util.stream.*;

public class LineStream {
    public static void main(String[] args) throws Exception {
        Path p = Paths.get("sample.txt");

        try (Stream<String> lines = Files.lines(p)) {
            lines.filter(l -> !l.isBlank())
                 .map(String::toUpperCase)
                 .forEach(System.out::println);
        }
    }
}

ポイント:

  • フィルタ → 変換 → 出力の流れが自然
  • 軽いログ処理に重宝する

おわりに:NIO.2、奥が深い

軽く触っただけでも「お〜便利だな〜」となりますが、
NIO.2 は 非同期 I/O(AsyncFileChannel)メモリマップドファイル など、まだまだ深掘りポイントがたくさんあります。

この先も、さるまりんのガレージで、
「もうちょっとプロっぽい I/O の書き方」を一緒に実験していければと思います。

読んでくださってありがとうございました。
それではまた!