Listowanie i przeszukiwanie katalogów przez walkFileTree
- Pliki i katalogi
- poziom: średni
- aktualizacja: 16 sierpnia 2026
Files.walkFileTree odwiedza drzewo katalogów przez kontrolowany FileVisitor. W odróżnieniu od krótkiego potoku Files.walk pozwala przed wejściem do podkatalogu zdecydować, że całe jego poddrzewo ma zostać pominięte. To ważne przy danych niezaufanych: dowiązanie do pliku mogłoby ujawnić treść spoza katalogu, a windowsowe złączenie katalogów prowadzić poza drzewo lub utworzyć pętlę. Przykład pomija oba rodzaje odwołań, ogranicza głębokość i czyta znalezione pliki w UTF-8 bez ładowania ich w całości do pamięci.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Stream;
/**
* Listowanie i przeszukiwanie katalogów przez walkFileTree — skryptyJAVA.pl
* Zapisz jako: PrzeszukiwanieKatalogow.java
*/
public class PrzeszukiwanieKatalogow {
/** Znajduje zwykłe pliki o rozszerzeniu, schodząc najwyżej na podaną głębokość. */
public static List<Path> znajdzPoRozszerzeniu(Path katalog, String rozszerzenie,
int maksymalnaGlebokosc) throws IOException {
if (maksymalnaGlebokosc < 0) {
throw new IllegalArgumentException("Głębokość nie może być ujemna.");
}
String koncowka = normalizujRozszerzenie(rozszerzenie);
List<Path> wyniki = new ArrayList<>();
Files.walkFileTree(katalog, Set.of(), maksymalnaGlebokosc, new SimpleFileVisitor<>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
if (!dir.equals(katalog) && (attrs.isSymbolicLink() || attrs.isOther())) {
return FileVisitResult.SKIP_SUBTREE;
}
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(Path plik, BasicFileAttributes attrs) {
if (attrs.isRegularFile() && maRozszerzenie(plik, koncowka)) {
wyniki.add(plik);
}
return FileVisitResult.CONTINUE;
}
});
wyniki.sort(Comparator.comparing(plik -> katalog.relativize(plik).toString(),
String.CASE_INSENSITIVE_ORDER));
return List.copyOf(wyniki);
}
/** Przed kropką musi coś stać: plik nazwany ".txt" jest ukryty, a nie typu txt. */
private static boolean maRozszerzenie(Path plik, String koncowka) {
String nazwa = plik.getFileName().toString().toLowerCase(Locale.ROOT);
return nazwa.length() > koncowka.length() && nazwa.endsWith(koncowka);
}
/** Wyszukuje fragment bez rozróżniania wielkości liter w plikach danego typu. */
public static List<Path> znajdzWTresci(Path katalog, String rozszerzenie,
String fragment) throws IOException {
String szukany = Objects.requireNonNull(fragment, "fragment").toLowerCase(Locale.ROOT);
List<Path> wyniki = new ArrayList<>();
for (Path plik : znajdzPoRozszerzeniu(katalog, rozszerzenie, Integer.MAX_VALUE)) {
if (plikZawiera(plik, szukany)) {
wyniki.add(plik);
}
}
return List.copyOf(wyniki);
}
/** Czyta plik linia po linii; strumień nie pozostaje otwarty po powrocie z metody. */
private static boolean plikZawiera(Path plik, String szukany) throws IOException {
try (BufferedReader czytnik = new BufferedReader(new InputStreamReader(
Files.newInputStream(plik, LinkOption.NOFOLLOW_LINKS),
StandardCharsets.UTF_8));
Stream<String> linie = czytnik.lines()) {
try {
return linie.anyMatch(linia -> linia.toLowerCase(Locale.ROOT).contains(szukany));
} catch (UncheckedIOException wyjatek) {
throw wyjatek.getCause();
}
}
}
private static String normalizujRozszerzenie(String rozszerzenie) {
String wynik = Objects.requireNonNull(rozszerzenie, "rozszerzenie")
.strip().toLowerCase(Locale.ROOT);
if (wynik.isEmpty() || wynik.equals(".")) {
throw new IllegalArgumentException("Podaj rozszerzenie, na przykład txt.");
}
return wynik.startsWith(".") ? wynik : "." + wynik;
}
/** Usuwa drzewo od najgłębszych elementów; używane tylko do sprzątania demonstracji. */
private static void usunDrzewo(Path katalog) throws IOException {
if (!Files.exists(katalog)) {
return;
}
List<Path> odKonca;
try (Stream<Path> sciezki = Files.walk(katalog)) {
try {
odKonca = sciezki.sorted(Comparator.reverseOrder()).toList();
} catch (UncheckedIOException wyjatek) {
throw wyjatek.getCause();
}
}
for (Path sciezka : odKonca) {
Files.deleteIfExists(sciezka);
}
}
// ── PRZYKŁADY ───────────────────────────────────────────
public static void main(String[] args) throws IOException {
Path demo = Files.createTempDirectory("files-walk-demo-");
try {
Path dokumenty = Files.createDirectories(demo.resolve("dokumenty"));
Path archiwum = Files.createDirectories(dokumenty.resolve("archiwum"));
Path kod = Files.createDirectories(demo.resolve("kod"));
Files.writeString(demo.resolve("README.TXT"), "Projekt w języku Java\n",
StandardCharsets.UTF_8);
Files.writeString(dokumenty.resolve("raport.txt"), "Raport kwartalny\nBez błędów\n",
StandardCharsets.UTF_8);
Files.writeString(archiwum.resolve("stary.txt"), "Archiwalny projekt Java\n",
StandardCharsets.UTF_8);
Files.writeString(kod.resolve("Main.java"), "public class Main {}\n",
StandardCharsets.UTF_8);
System.out.println("Katalog demonstracyjny: " + demo);
System.out.println("\nPliki TXT do głębokości 2:");
for (Path plik : znajdzPoRozszerzeniu(demo, "txt", 2)) {
System.out.printf(" %-25s %4d B%n", demo.relativize(plik), Files.size(plik));
}
System.out.println("\nPliki TXT zawierające słowo Java:");
for (Path plik : znajdzWTresci(demo, ".txt", "java")) {
System.out.println(" " + demo.relativize(plik));
}
} finally {
usunDrzewo(demo);
System.out.println("\nUsunięto katalog demonstracyjny.");
}
}
}
Jak użyć tego skryptu
- Zapisz kod jako
PrzeszukiwanieKatalogow.java— nazwa pliku musi być identyczna z nazwą klasy publicznej — i uruchom poleceniemjava PrzeszukiwanieKatalogow.java. Program tworzy niewielkie drzewo w katalogu tymczasowym, wykonuje dwa wyszukania i usuwa wszystkie dane w blokufinally. - Przekaż własny katalog jako
Path.of("dane")albo ścieżkę bezwzględną. MetodaznajdzPoRozszerzeniuprzyjmuje rozszerzenie zarówno z kropką, jak i bez niej. - Ustaw trzeci argument metody
znajdzPoRozszerzeniuna żądaną głębokość:1obejmuje sam katalog i jego bezpośrednią zawartość, aInteger.MAX_VALUEoznacza całe drzewo. - Do wyszukiwania treści wywołaj
znajdzWTresci(katalog, "txt", "szukany fragment"). Metoda nie rozróżnia wielkości liter i czyta kolejne linie jako UTF-8.
Warto wiedzieć
walkFileTreezamyka uchwyty do katalogów w trakcie marszu, ale strumień linii otwarty przezBufferedReader.lines()nadal wymagatry-with-resources. Na Windowsie pozostawiony uchwyt potrafi zablokować usunięcie lub zmianę nazwy pliku.- Visitor pomija zagnieżdżone ścieżki raportowane bez podążania jako dowiązanie symboliczne albo obiekt
isOther(); ten drugi warunek zatrzymuje typowe windowsowe złączenia katalogów. Twardy link wygląda jednak jak zwykły plik i standardowe API nie zdradza, gdzie utworzono jego pozostałe nazwy — jeśli katalog jest pod kontrolą napastnika, potrzebujesz dodatkowej izolacji systemowej. - Przeszukuj treść wyłącznie tych plików, o których wiesz, że są tekstem. Nie próbuj dekodować wszystkiego, co napotkasz — obraz, archiwum czy plik binarny nie jest poprawnym UTF-8 i odczyt skończy się wyjątkiem w środku strumienia.
- Domyślna implementacja
SimpleFileVisitor.visitFileFailedponownie rzuca wyjątek, więc jeden podkatalog bez uprawnień nadal przerywa cały skan. Jeśli skan ma przeżyć takie miejsca, przesłoń tę metodę i dla oczekiwanych błędów zwracajFileVisitResult.CONTINUE; pozostałych wyjątków nie połykaj, bo ukryjesz awarię dysku albo błędną ścieżkę.
Podobne skrypty
Odczyt i zapis plików tekstowych
Komplet operacji na plikach tekstowych przez java.nio.file.Files z jawnym UTF-8: zapis, dopisywanie, odczyt całości i linia po linii, przetwarzanie strumieniowe, kopiowanie i usuwanie.
początkujący Zobacz kod → Pliki i katalogiEksport i import CSV ze średnikiem
Samodzielny zapis i odczyt CSV w UTF-8 ze średnikiem: cytowanie, pola wielowierszowe, BOM i CRLF pod Excela, ścisły parser oraz jawny tryb chroniący arkusz przed wykonaniem formuł z niezaufanych danych.
zaawansowany Zobacz kod → Pliki i katalogiMiniatury obrazków bez zewnętrznych bibliotek
Skalowanie obrazków na czystym JDK: ImageIO wczytuje plik, Graphics2D z podpowiedziami jakości robi miniaturę z zachowaniem proporcji, a przezroczyste PNG spłaszczasz na biel przed zapisem do JPEG.
średni Zobacz kod →