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 のすべての機能 |
| 例 | |
|
| 再帰性 | |
組み込みのサポートなし |
| 使いどころ | ファイルのフィルタリング | 複雑な検査 |
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 を必ず閉じる!
重要!
- DirectoryStream と Files.find/Files.walk が返すストリームは、必ずクローズすべきリソースです。
- try-with-resources を使用しましょう:
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, "*.java")) {
for (Path entry : stream) {
// ...
}
}
- ストリームを閉じないとリソースリークが発生する可能性があります(例: 「開いているファイルが多すぎます」)。
- Files.find と Files.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: DirectoryStream や Stream<Path> を閉じ忘れてリソースリークを起こす。
エラー No.4: 簡単なフィルタに過度に複雑な regex パターンを使う — 可能なら glob を使う。
エラー No.5: 補助ディレクトリ(".git", "node_modules")を除外していない — 検索が遅くなり、不要なファイルでノイズが増える。
GO TO FULL VERSION