CodeGym /コース /JAVA 25 SELF /グロブ/PathMatcher, DirectoryStream.Filter

グロブ/PathMatcher, DirectoryStream.Filter

JAVA 25 SELF
レベル 39 , レッスン 2
使用可能

1. はじめに

ファイルやフォルダを扱うとき、特定のファイルだけを選びたいことがよくあります。たとえば、すべての ".java" ファイル、すべての画像、特定の日付のログなどです。このために Java(および他の環境)では グロブglob)や 正規表現regex)が使われます。

  • グロブ — コマンドライン(Linux/Windows)に近い形で、特殊記号(*, ?, [], {})を使ってファイル名パターンを簡単に記述する方法。
  • Regex — 複雑なパターンに対応できる強力な正規表現言語。

グロブパターンの例

  • *.java — 現在のフォルダ内のすべての ".java" ファイル。
  • **/*.java — すべてのサブフォルダ内の ".java" ファイル(「**」は再帰的検索)。
  • *.{png,jpg}".png" または ".jpg" の拡張子を持つファイル。
  • file-??.log"file-01.log""file-AB.log" のような(任意の2文字)。
  • [A-Z]*.txt — 先頭が大文字の ".txt" ファイル。

グロブは regex より簡単で、ファイルのフィルタリングではたいてい十分です。

glob と regex の比較

特徴 glob regex
簡単さ とても簡単 難しいが強力
記号
*, ?, [], {}
regex のすべての機能
*.java
.*\.java
再帰性
**/*.java
組み込みのサポートなし
使いどころ ファイルのフィルタリング 複雑な検査

2. PathMatcher: パターンでファイルをフィルタリング

Java NIO(java.nio.file)では、パターンによるファイルのフィルタリングに PathMatcher インターフェースを使います。これは FileSystems.getDefault().getPathMatcher(...) から取得できます。

構文

PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:*.java");
  • "glob:*.java" — グロブのパターン。
  • "regex:.*\\.java" — 正規表現パターン。

例: フォルダ内のファイルをフィルタリング

import java.nio.file.*;

Path dir = Paths.get("src");
PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:*.java");

try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
    for (Path entry : stream) {
        if (matcher.matches(entry.getFileName())) {
            System.out.println(entry);
        }
    }
}

重要:

  • matcher.matches() は通常ファイル名のみをチェックします — フルパスではなく entry.getFileName() を渡してください。
  • 再帰的な検索には Files.walk() または Files.find() を使用します。

Files.walk() は NIO2 のメソッドで、指定ディレクトリ配下のファイルとフォルダを再帰的に含む Stream<Path> を返します。 DirectoryStream が 1 階層のみを対象とするのに対し、Files.walk() は Stream API でディレクトリツリーを扱えます。

使用例:

import java.nio.file.*;
import java.util.stream.Stream;

Path start = Paths.get("src");
try (Stream<Path> stream = Files.walk(start)) { // すべてのサブディレクトリを再帰的に走査
    stream.filter(Files::isRegularFile)
          .forEach(System.out::println);
}

regex の例

PathMatcher matcher = FileSystems.getDefault().getPathMatcher("regex:.*\\.(png|jpg)");

3. Files.newDirectoryStream: ディレクトリを読む際のフィルタリング

Files.newDirectoryStream() は、パターンや独自のフィルタでファイルを即座に絞り込めます。

グロブパターンでのフィルタリング

try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, "*.java")) {
    for (Path entry : stream) {
        System.out.println(entry);
    }
}

第2引数はグロブパターン("glob:" の接頭辞なし)。

DirectoryStream.Filter によるフィルタリング

より複雑なロジック(サイズ・日付での絞り込み、フォルダの除外など)が必要な場合は、DirectoryStream.Filter<Path> を使います:

DirectoryStream.Filter<Path> filter = path -> {
    // 例: ファイルのみ。ディレクトリは除外し、.git と node_modules も除外
    return Files.isRegularFile(path)
        && !path.getFileName().toString().equals(".git")
        && !path.getFileName().toString().equals("node_modules");
};

try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, filter)) {
    for (Path entry : stream) {
        System.out.println(entry);
    }
}

4. Files.find: BiPredicate を使った強力な検索

日付・サイズ・名前など複数条件で(再帰的に)ファイルを探す必要がある場合は、Files.find を使います。

構文

Stream<Path> stream = Files.find(
    startDir, // どこを探すか
    maxDepth, // 深さ(Integer.MAX_VALUE — 再帰的)
    (path, attrs) -> {
        // path — ファイルへのパス
        // attrs — ファイル属性(サイズ、日時など)
        return path.getFileName().toString().endsWith(".log")
            && attrs.size() > 1024; // 大きなログのみ
    }
);

stream.forEach(System.out::println);
  • 第2引数 — 検索の最大深さ。
  • 第3引数 — BiPredicate<Path, BasicFileAttributes>: 条件を満たす場合は true を返します。

例: .git と node_modules を除外する

Stream<Path> stream = Files.find(
    Paths.get("."),
    Integer.MAX_VALUE,
    (path, attrs) -> {
        String name = path.getFileName().toString();
        // .git と node_modules ディレクトリを除外
        if (name.equals(".git") || name.equals("node_modules")) return false;
        // 1 MB より大きい .log ファイルのみ
        return name.endsWith(".log") && attrs.size() > 1024 * 1024;
    }
);
stream.forEach(System.out::println);

5. 実践

例 1: .git と node_modules を除くすべての .log ファイルを見つける

Files.walk(Paths.get("."))
    .filter(path -> {
        String name = path.getFileName().toString();
        // ディレクトリを除外
        if (name.equals(".git") || name.equals("node_modules")) return false;
        // .log ファイルのみ
        return name.endsWith(".log");
    })
    .forEach(System.out::println);

例 2: 指定した日時以降に作成された画像を探す

import java.nio.file.attribute.BasicFileAttributes;
import java.time.Instant;

Instant after = Instant.parse("2024-06-01T00:00:00Z");

Files.find(
    Paths.get("images"),
    Integer.MAX_VALUE,
    (path, attrs) -> {
        String name = path.getFileName().toString();
        return (name.endsWith(".png") || name.endsWith(".jpg"))
            && attrs.creationTime().toInstant().isAfter(after);
    }
).forEach(System.out::println);

例 3: .git を除き、巨大なファイル(10 MB 超)を探す

Files.find(
    Paths.get("."),
    Integer.MAX_VALUE,
    (path, attrs) -> {
        String name = path.getFileName().toString();
        return !name.equals(".git") && attrs.size() > 10 * 1024 * 1024;
    }
).forEach(System.out::println);

6. 役に立つポイント

glob 構文クイックリファレンス

  • * — 任意の文字列(パス区切り / は除く)
  • ** — 任意の階層のディレクトリ(Java では有効)
  • ? — ちょうど 1 文字
  • [abc] — a, b, c のいずれか
  • [a-z] — 範囲内の任意の文字
  • {a,b,c} — 列挙のいずれか(例: *.{png,jpg}

例:

  • *.java — 現在のフォルダ内の ".java" ファイルすべて
  • **/*.java — すべてのサブフォルダ内の ".java" ファイル
  • file-??.log"file-01.log""file-AB.log" のようなファイル
  • [A-Z]*.txt — 先頭が大文字の ".txt" ファイル

パフォーマンスとリーク: DirectoryStream を必ず閉じる!

重要!

  • DirectoryStreamFiles.find/Files.walk が返すストリームは、必ずクローズすべきリソースです。
  • try-with-resources を使用しましょう:
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, "*.java")) {
    for (Path entry : stream) {
        // ...
    }
}
  • ストリームを閉じないとリソースリークが発生する可能性があります(例: 「開いているファイルが多すぎます」)。
  • Files.findFiles.walk では、close() を呼ぶか、try-with-resources を必ず使ってください:
try (Stream<Path> stream = Files.find(...)) {
    stream.forEach(System.out::println);
}

7. まとめとよくあるミス

エラー No.1: グロブパターンで * は再帰検索しない。再帰には "**/*.java" または Files.walk を使う。

エラー No.2: matcher.matches() にフルパスを渡してしまう — 通常はファイル名(getFileName())だけを渡す。

エラー No.3: DirectoryStreamStream<Path> を閉じ忘れてリソースリークを起こす。

エラー No.4: 簡単なフィルタに過度に複雑な regex パターンを使う — 可能なら glob を使う。

エラー No.5: 補助ディレクトリ(".git", "node_modules")を除外していない — 検索が遅くなり、不要なファイルでノイズが増える。

1
タスク
JAVA 25 SELF, レベル 39, レッスン 2
ロック未解除
データアナリスト:重要なレポートの抽出
データアナリスト:重要なレポートの抽出
1
タスク
JAVA 25 SELF, レベル 39, レッスン 2
ロック未解除
ギャラリーのキュレーター:画像の棚卸し
ギャラリーのキュレーター:画像の棚卸し
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION