1. Iterare i file in una cartella
Ottenere l’elenco dei file: Files.list e Files.walk
In Java ci sono due metodi molto utili per lavorare con il contenuto di una cartella:
- Files.list(Path) — restituisce uno stream (Stream<Path>) di tutti i file e cartelle di primo livello nella cartella specificata.
- Files.walk(Path) — restituisce lo stream di tutti i file e cartelle nella cartella e in tutte le sue sottocartelle (in modo ricorsivo).
Esempio: stampa di tutti i file e cartelle nella cartella data
import java.nio.file.*;
import java.io.IOException;
public class ListFilesExample {
public static void main(String[] args) throws IOException {
Path dir = Paths.get("data");
// Otteniamo lo stream di tutti i file e cartelle di primo livello
try (var stream = Files.list(dir)) {
stream.forEach(System.out::println);
}
}
}
Se vuoi attraversare tutte le sottocartelle (ricorsivamente), usa Files.walk:
try (var stream = Files.walk(dir)) {
stream.forEach(System.out::println);
}
Attenzione:
Files.walk può restituire moltissimi file se la struttura è grande — non eseguirlo alla radice del disco se non vuoi vedere «l’elenco dell’intero universo»!
Visualizzazione: differenza tra list e walk
| Metodo | Cosa restituisce |
|---|---|
|
Solo il contenuto della cartella corrente |
|
Tutti i file e le cartelle, incluse quelle nidificate |
2. Filtraggio dei file
Uno dei principali vantaggi nel lavorare con gli stream (Stream<Path>) è la possibilità di filtrare i file per qualsiasi criterio: estensione, nome, dimensione, data di creazione, ecc.
Filtraggio per estensione
Supponiamo ci servano solo i file .txt. Possiamo filtrarli così:
try (var stream = Files.list(dir)) {
stream
.filter(path -> path.toString().endsWith(".txt"))
.forEach(System.out::println);
}
Filtraggio per nome
Vogliamo tutti i file il cui nome inizia con "report":
stream
.filter(path -> path.getFileName().toString().startsWith("report"))
.forEach(System.out::println);
Filtraggio per dimensione
Otteniamo solo i file «pesanti» (maggiore di 1 MB):
stream
.filter(path -> {
try {
return Files.size(path) > 1_000_000;
} catch (IOException e) {
return false;
}
})
.forEach(System.out::println);
Filtraggio per data
Ad esempio, i file modificati negli ultimi 7 giorni:
import java.time.*;
import java.nio.file.attribute.*;
stream
.filter(path -> {
try {
FileTime lastModified = Files.getLastModifiedTime(path);
return lastModified.toInstant().isAfter(Instant.now().minus(Duration.ofDays(7)));
} catch (IOException e) {
return false;
}
})
.forEach(System.out::println);
Tutto insieme: filtraggio combinato
Puoi combinare i filtri «come al ristorante»: ad esempio, tutti i .log-file più vecchi di un mese e maggiori di 10 KB:
stream
.filter(path -> path.toString().endsWith(".log"))
.filter(path -> {
try {
return Files.size(path) > 10_000;
} catch (IOException e) {
return false;
}
})
.filter(path -> {
try {
FileTime lastModified = Files.getLastModifiedTime(path);
return lastModified.toInstant().isBefore(Instant.now().minus(Duration.ofDays(30)));
} catch (IOException e) {
return false;
}
})
.forEach(System.out::println);
3. Copia ed eliminazione di file in massa
Le operazioni in massa sono semplici: ottieni uno stream di file e poi applichi la funzione necessaria a ciascun elemento. La cosa importante — non dimenticare i possibili errori (ad esempio, file in uso o già esistente).
Copiare tutti i file da una cartella a un’altra
import java.nio.file.StandardCopyOption;
Path sourceDir = Paths.get("data");
Path targetDir = Paths.get("backup");
try (var stream = Files.list(sourceDir)) {
stream.forEach(path -> {
Path targetPath = targetDir.resolve(path.getFileName());
try {
Files.copy(path, targetPath, StandardCopyOption.REPLACE_EXISTING);
System.out.println("Copiato: " + path.getFileName());
} catch (IOException e) {
System.out.println("Errore di copia " + path.getFileName() + ": " + e.getMessage());
}
});
}
Eliminare tutti i file temporanei (*.tmp)
try (var stream = Files.list(dir)) {
stream
.filter(path -> path.toString().endsWith(".tmp"))
.forEach(path -> {
try {
Files.delete(path);
System.out.println("Eliminato: " + path.getFileName());
} catch (IOException e) {
System.out.println("Errore di eliminazione " + path.getFileName() + ": " + e.getMessage());
}
});
}
Uso dello Stream API per elaborare raccolte di percorsi
try (var stream = Files.list(dir)) {
stream
.filter(path -> path.toString().endsWith(".txt"))
.sorted((p1, p2) -> {
try {
return Long.compare(Files.size(p2), Files.size(p1)); // per dimensione, decrescente
} catch (IOException e) {
return 0;
}
})
.limit(5) // solo i 5 più grandi
.forEach(System.out::println);
}
4. Attività pratiche
Ricerca ed eliminazione dei file più vecchi di una certa data
Eliminiamo tutti i file che non sono stati modificati da oltre un anno:
try (var stream = Files.list(dir)) {
stream
.filter(path -> {
try {
FileTime lastModified = Files.getLastModifiedTime(path);
return lastModified.toInstant().isBefore(Instant.now().minus(Duration.ofDays(365)));
} catch (IOException e) {
return false;
}
})
.forEach(path -> {
try {
Files.delete(path);
System.out.println("Eliminato: " + path.getFileName());
} catch (IOException e) {
System.out.println("Errore di eliminazione " + path.getFileName() + ": " + e.getMessage());
}
});
}
Rinomina massiva dei file secondo un modello
Supponiamo di dover aggiungere a ogni file .txt il prefisso "old_":
try (var stream = Files.list(dir)) {
stream
.filter(path -> path.toString().endsWith(".txt"))
.forEach(path -> {
Path newPath = path.resolveSibling("old_" + path.getFileName());
try {
Files.move(path, newPath);
System.out.println("Rinominato: " + path.getFileName() + " -> " + newPath.getFileName());
} catch (IOException e) {
System.out.println("Errore di rinomina " + path.getFileName() + ": " + e.getMessage());
}
});
}
Copia di tutti i file da tutte le sottocartelle (ricorsivamente)
Usiamo Files.walk per iterare tutti i file in tutte le sottocartelle:
try (var stream = Files.walk(sourceDir)) {
stream
.filter(Files::isRegularFile)
.forEach(path -> {
Path relative = sourceDir.relativize(path);
Path targetPath = targetDir.resolve(relative);
try {
Files.createDirectories(targetPath.getParent());
Files.copy(path, targetPath, StandardCopyOption.REPLACE_EXISTING);
System.out.println("Copiato: " + path + " -> " + targetPath);
} catch (IOException e) {
System.out.println("Errore di copia " + path + ": " + e.getMessage());
}
});
}
5. Aspetti importanti e particolarità
Gestione degli errori nelle operazioni in massa
Nelle operazioni in massa quasi sempre si incontrano file «problematici»: accesso negato, file in uso, percorso troppo lungo, file già esistente, ecc. Se non gestisci le eccezioni all’interno del ciclo, il programma può «andare in crash» al primo errore. Quindi usa sempre try-catch dentro forEach, per «inghiottire» l’errore per un singolo file ma continuare con gli altri.
Impatto della ricorsione sulle prestazioni e sullo stack delle chiamate
Usando Files.walk per cartelle grandi puoi ottenere attraversamenti molto lunghi, se implementi la ricorsione manualmente. Per fortuna, il metodo è implementato in modo efficiente e non provoca overflow dello stack. Ma se scrivi una tua funzione ricorsiva per eliminare/copiare cartelle, fai attenzione: per strutture enormemente annidate lo stack può «scoppiare».
Lavorare con cartelle grandi
- Usa Files.walk con cautela: se nella cartella ci sono migliaia di file, può richiedere tempo e consumare molta memoria.
- Se devi elaborare solo il livello superiore — usa Files.list.
- Per cercare file per pattern usa il filtraggio per nome (endsWith, matches, ecc.).
Esempio: «Pulire la cartella dai file temporanei»
Path tempDir = Paths.get("data/tmp");
try (var stream = Files.walk(tempDir)) {
stream
.filter(Files::isRegularFile)
.filter(path -> path.toString().endsWith(".tmp"))
.forEach(path -> {
try {
Files.delete(path);
System.out.println("Eliminato: " + path);
} catch (IOException e) {
System.out.println("Errore di eliminazione " + path + ": " + e.getMessage());
}
});
}
Metodi principali per le operazioni in massa
| Operazione | Metodo/approccio | Caratteristiche |
|---|---|---|
| Ottenere tutti i file | |
walk — ricorsivo, list — solo livello superiore |
| Filtraggio per estensione | |
Combinabile con altri filtri |
| Copia di un gruppo di file | |
Verifica l’esistenza della cartella di destinazione |
| Eliminazione di un gruppo di file | |
Meglio usare try-catch per ogni operazione |
| Rinomina massiva | |
La rinomina è un move con un nuovo nome |
6. Errori tipici nelle operazioni in massa
Errore n. 1: Eccezioni non gestite all’interno di forEach.
Un problema molto comune: se non racchiudi ogni operazione in try-catch, il programma «andrà in crash» al primo file problematico e gli altri non verranno elaborati.
Errore n. 2: Tentare di eliminare/copiare una cartella come file (o viceversa).
I metodi Files.delete e Files.copy trattano file e cartelle in modo diverso. Non confonderli! Ad esempio, tentare di eliminare una cartella non vuota con il metodo standard genererà un errore.
Errore n. 3: Costruzione errata del percorso di destinazione durante la copia.
Se costruisci il percorso di destinazione senza tenere conto della struttura (ad esempio, non usare relativize), puoi sovrascrivere file o ottenere una struttura sbagliata nell’archivio.
Errore n. 4: Aprire un numero troppo elevato di file contemporaneamente.
Se apri stream per lettura/scrittura all’interno di un ciclo, non dimenticare di chiuderli! Meglio usare try-with-resources.
Errore n. 5: Attraversamento ricorsivo senza limitare la profondità.
Usando Files.walk su cartelle molto grandi e profonde puoi incontrare problemi di prestazioni e memoria. Se devi limitare la profondità — usa la versione sovraccaricata con il parametro di profondità: Files.walk(dir, depth).
Errore n. 6: Ignorare implicitamente file nascosti/sistema.
Se l’applicazione lavora con file dell’utente, potresti toccare accidentalmente file nascosti o di sistema. Per filtrarli usa il metodo Files.isHidden(path).
GO TO FULL VERSION