JavaRush /Курси /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" (два будь-які символи).
  • [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, який показує лише вміст однієї папки, 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") — пошук стає повільним і «засмічується» зайвими файлами.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ