CodeGym /Kursy /JAVA 25 SELF /Globbing/PathMatcher, DirectoryStream.Filter

Globbing/PathMatcher, DirectoryStream.Filter

JAVA 25 SELF
Poziom 39 , Lekcja 2
Dostępny

1. Wprowadzenie

Gdy pracujesz z plikami i katalogami, często trzeba wybrać tylko określone pliki: na przykład wszystkie ".java"-pliki, wszystkie obrazy, wszystkie logi za określoną datę. W tym celu w Javie (i nie tylko) używa się globbingu (glob) i wyrażeń regularnych (regex).

  • Globbing — prosty sposób opisywania wzorca nazwy pliku za pomocą znaków specjalnych (*, ?, [], {}), jak w wierszu poleceń systemu Linux lub Windows.
  • Regex — potężny język wyrażeń regularnych do złożonych wzorców.

Przykłady wzorców glob

  • *.java — wszystkie pliki z rozszerzeniem ".java" w bieżącym katalogu.
  • **/*.java — wszystkie ".java"-pliki we wszystkich podkatalogach (podwójna gwiazdka — wyszukiwanie rekurencyjne).
  • *.{png,jpg} — wszystkie pliki z rozszerzeniem ".png" lub ".jpg".
  • file-??.log — pliki w rodzaju "file-01.log", "file-AB.log" (dwa dowolne znaki).
  • [A-Z]*.txt — wszystkie ".txt"-pliki rozpoczynające się wielką literą.

Globbing jest prostszy niż regex i najczęściej w zupełności wystarcza do filtrowania plików.

Porównanie glob i regex

Cecha glob regex
Prostota Bardzo proste Bardziej złożone, ale potężniejsze
Symbole
*, ?, [], {}
Pełne możliwości regex
Przykłady
*.java
.*\.java
Rekurencyjność
**/*.java
Brak wbudowanej obsługi
Kiedy używać Filtrowanie plików Złożone sprawdzenia

2. PathMatcher: filtrowanie plików według wzorca

W Java NIO (java.nio.file) do filtrowania plików według wzorca używa się interfejsu PathMatcher. Można go uzyskać przez FileSystems.getDefault().getPathMatcher(...).

Składnia

PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:*.java");
  • "glob:*.java" — wzorzec glob.
  • "regex:.*\\.java" — wzorzec wyrażenia regularnego.

Przykład: filtrowanie plików w katalogu

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);
        }
    }
}

Ważne:

  • matcher.matches() zazwyczaj sprawdza tylko nazwę pliku — przekazuj entry.getFileName(), a nie pełną ścieżkę.
  • Do wyszukiwania rekurencyjnego używaj Files.walk() lub Files.find().

Files.walk() — metoda NIO2 zwracająca Stream<Path> ze wszystkimi plikami i katalogami w wskazanym katalogu rekurencyjnie, w tym podkatalogi. W odróżnieniu od DirectoryStream, który pokazuje tylko zawartość jednego katalogu, Files.walk() pozwala pracować z drzewem katalogów przez Stream API.

Przykład użycia:

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

Path start = Paths.get("src");
try (Stream<Path> stream = Files.walk(start)) { // rekursywnie wszystkie podkatalogi
    stream.filter(Files::isRegularFile)
          .forEach(System.out::println);
}

Przykład z regex

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

3. Files.newDirectoryStream: filtrowanie podczas przeglądania katalogu

Metoda Files.newDirectoryStream() pozwala od razu filtrować pliki według wzorca lub za pomocą własnego filtra.

Filtrowanie według wzorca glob

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

Drugi parametr to wzorzec glob (bez prefiksu "glob:").

Filtrowanie za pomocą DirectoryStream.Filter

Jeśli potrzebna jest bardziej złożona logika (np. filtrowanie po rozmiarze, dacie, wykluczenie katalogów), użyj DirectoryStream.Filter<Path>:

DirectoryStream.Filter<Path> filter = path -> {
    // Przykład: tylko pliki, nie katalogi, oraz bez .git i 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: zaawansowane wyszukiwanie z BiPredicate

Jeśli trzeba wyszukiwać pliki wg złożonych warunków (np. po dacie, rozmiarze, nazwie, rekurencyjnie), użyj Files.find.

Składnia

Stream<Path> stream = Files.find(
    startDir, // gdzie szukać
    maxDepth, // głębokość (Integer.MAX_VALUE — rekurencyjnie)
    (path, attrs) -> {
        // path — ścieżka do pliku
        // attrs — atrybuty pliku (rozmiar, data itp.)
        return path.getFileName().toString().endsWith(".log")
            && attrs.size() > 1024; // tylko duże logi
    }
);

stream.forEach(System.out::println);
  • Drugi parametr — maksymalna głębokość wyszukiwania.
  • Trzeci — BiPredicate<Path, BasicFileAttributes>: zwraca true, jeśli plik pasuje.

Przykład: wykluczyć katalogi .git i node_modules

Stream<Path> stream = Files.find(
    Paths.get("."),
    Integer.MAX_VALUE,
    (path, attrs) -> {
        String name = path.getFileName().toString();
        // Wykluczamy katalogi .git i node_modules
        if (name.equals(".git") || name.equals("node_modules")) return false;
        // Tylko pliki .log o rozmiarze większym niż 1 MB
        return name.endsWith(".log") && attrs.size() > 1024 * 1024;
    }
);
stream.forEach(System.out::println);

5. Praktyka

Przykład 1: Znaleźć wszystkie pliki .log, z wyjątkiem .git i node_modules

Files.walk(Paths.get("."))
    .filter(path -> {
        String name = path.getFileName().toString();
        // Wykluczamy katalogi
        if (name.equals(".git") || name.equals("node_modules")) return false;
        // Tylko pliki .log
        return name.endsWith(".log");
    })
    .forEach(System.out::println);

Przykład 2: Znaleźć wszystkie obrazy utworzone po określonej dacie

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);

Przykład 3: Znaleźć wszystkie duże pliki (więcej niż 10 MB), z wyjątkiem .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. Przydatne niuanse

Krótka ściągawka składni glob

  • * — dowolna liczba dowolnych znaków (z wyjątkiem separatora /)
  • ** — dowolna liczba dowolnych katalogów (działa w Javie)
  • ? — dokładnie jeden dowolny znak
  • [abc] — dowolny ze znaków a, b, c
  • [a-z] — dowolny znak z zakresu
  • {a,b,c} — dowolna z wymienionych wartości (na przykład *.{png,jpg})

Przykłady:

  • *.java — wszystkie ".java"-pliki w bieżącym katalogu
  • **/*.java — wszystkie ".java"-pliki we wszystkich podkatalogach
  • file-??.log — pliki w rodzaju "file-01.log", "file-AB.log"
  • [A-Z]*.txt — wszystkie ".txt"-pliki rozpoczynające się wielką literą

Wydajność i wycieki: zamykaj DirectoryStream!

Ważne!

  • DirectoryStream i strumienie z Files.find/Files.walk — to zasoby, które trzeba zamykać.
  • Używaj try-with-resources:
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, "*.java")) {
    for (Path entry : stream) {
        // ...
    }
}
  • Jeśli nie zamkniesz strumienia, może dojść do wycieku zasobów (np. „zbyt wiele otwartych plików”).
  • Dla Files.find i Files.walk — koniecznie wywołuj close() lub używaj try-with-resources:
try (Stream<Path> stream = Files.find(...)) {
    stream.forEach(System.out::println);
}

7. Podsumowanie i typowe błędy

Błąd nr 1: Używanie wzorca glob bez świadomości, że * nie wyszukuje rekurencyjnie. Do rekurencji użyj "**/*.java" lub Files.walk.

Błąd nr 2: Przekazywanie pełnej ścieżki do matcher.matches() — zazwyczaj należy przekazywać tylko nazwę pliku (getFileName()).

Błąd nr 3: Zapomniano zamknąć DirectoryStream lub Stream<Path> — skończy się to wyciekiem zasobów.

Błąd nr 4: Zbyt skomplikowane regex-wzorce do prostej filtracji — użyj glob, jeśli wystarczy.

Błąd nr 5: Nie wykluczono katalogów pomocniczych (".git", "node_modules") — wyszukiwanie robi się wolne i „zaśmieca się” zbędnymi plikami.

1
Zadanie
JAVA 25 SELF, poziom 39, lekcja 2
Niedostępne
Analityk danych: selekcja krytycznych raportów
Analityk danych: selekcja krytycznych raportów
1
Zadanie
JAVA 25 SELF, poziom 39, lekcja 2
Niedostępne
Kurator galerii: inwentaryzacja obrazów
Kurator galerii: inwentaryzacja obrazów
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION