1. Введение
Когда вы работаете с файлами и папками, часто нужно выбрать только определённые файлы: например, все ".java"-файлы, все картинки, все логи за определённую дату. Для этого в Java (и не только) используют глоббинг (glob) и регулярные выражения (regex).
- Глоббинг — простой способ описать шаблон имени файла с помощью специальных символов (*, ?, [], {}), как в командной строке Linux или Windows.
- Regex — мощный язык регулярных выражений для сложных шаблонов.
Примеры глоббинг-шаблонов
- *.java — все файлы с расширением ".java" в текущей папке.
- **/*.java — все ".java"-файлы во всех подпапках (двойная звёздочка — рекурсивный поиск).
- *.{png,jpg} — все файлы с расширением ".png" или ".jpg".
- file-??.log — файлы вроде "file-01.log", "file-AB.log" (два любых символа).
- [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, который показывает только содержимое одной папки, 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() позволяет сразу фильтровать файлы по маске или с помощью собственного фильтра.
Фильтрация по glob-шаблону
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, "*.java")) {
for (Path entry : stream) {
System.out.println(entry);
}
}
Второй параметр — это glob-шаблон (без префикса "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);
- Второй параметр — максимальная глубина поиска.
- Третий — 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;
// Только .log-файлы размером больше 1 МБ
return name.endsWith(".log") && attrs.size() > 1024 * 1024;
}
);
stream.forEach(System.out::println);
5. Практика
Пример 1: Найти все .log-файлы, кроме .git и node_modules
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: Найти все большие файлы (больше 10 МБ), кроме .git
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 работает)
- ? — ровно один любой символ
- [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. Итоги и типичные ошибки
Ошибка №1: Использование glob-шаблона без понимания, что * не ищет рекурсивно. Для рекурсии используйте "**/*.java" или Files.walk.
Ошибка №2: Передача полного пути в matcher.matches() — обычно нужно передавать только имя файла (getFileName()).
Ошибка №3: Забыли закрыть DirectoryStream или Stream<Path> — получите утечку ресурсов.
Ошибка №4: Слишком сложные regex-шаблоны для простой фильтрации — используйте glob, если хватает.
Ошибка №5: Не исключили служебные папки (".git", "node_modules") — поиск становится медленным и «засоряется» лишними файлами.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ