1. Itération des fichiers dans un répertoire
Obtention de la liste des fichiers : Files.list et Files.walk
En Java, pour travailler avec le contenu d’un dossier, il existe deux méthodes très utiles :
- Files.list(Path) — renvoie un flux (Stream<Path>) de tous les fichiers et dossiers de premier niveau dans le répertoire donné.
- Files.walk(Path) — renvoie un flux de tous les fichiers et dossiers dans le répertoire et dans tous ses sous-dossiers (récursivement).
Exemple : affichage de tous les fichiers et dossiers du répertoire 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");
// Nous obtenons le flux de tous les fichiers et dossiers du niveau supérieur
try (var stream = Files.list(dir)) {
stream.forEach(System.out::println);
}
}
}
Si vous souhaitez parcourir tous les sous-dossiers (récursivement), utilisez Files.walk :
try (var stream = Files.walk(dir)) {
stream.forEach(System.out::println);
}
Attention :
Files.walk peut renvoyer énormément de fichiers si la structure est vaste — ne lancez pas cela à la racine du disque si vous ne voulez pas voir « la liste de tout l’univers » !
Visualisation : différence entre list et walk
| Méthode | Ce qu’elle renvoie |
|---|---|
|
Uniquement le contenu du dossier courant |
|
Tous les fichiers et dossiers, y compris ceux imbriqués |
2. Filtrage des fichiers
L’un des principaux avantages des flux (Stream<Path>) est la possibilité de filtrer les fichiers selon n’importe quel critère : extension, nom, taille, date de création, etc.
Filtrage par extension
Supposons que nous ne voulons que les fichiers .txt. On peut les filtrer ainsi :
try (var stream = Files.list(dir)) {
stream
.filter(path -> path.toString().endsWith(".txt"))
.forEach(System.out::println);
}
Filtrage par nom
Nous voulons tous les fichiers dont le nom commence par "report" :
stream
.filter(path -> path.getFileName().toString().startsWith("report"))
.forEach(System.out::println);
Filtrage par taille
Récupérons uniquement les fichiers « lourds » (plus de 1 Mo) :
stream
.filter(path -> {
try {
return Files.size(path) > 1_000_000;
} catch (IOException e) {
return false;
}
})
.forEach(System.out::println);
Filtrage par date
Par exemple, les fichiers modifiés au cours des 7 derniers jours :
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);
Tout ensemble : filtrage combiné
On peut combiner les filtres « comme au restaurant » : par exemple, tous les fichiers .log plus vieux qu’un mois et de plus de 10 Ko :
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. Copie et suppression en masse de fichiers
Les opérations de masse, c’est simple : on obtient un flux de fichiers, puis on applique la fonction voulue à chaque élément. L’essentiel est de ne pas oublier les erreurs possibles (par exemple, fichier en cours d’utilisation ou déjà existant).
Copier tous les fichiers d’un dossier vers un autre
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("Copié : " + path.getFileName());
} catch (IOException e) {
System.out.println("Erreur de copie " + path.getFileName() + " : " + e.getMessage());
}
});
}
Suppression de tous les fichiers temporaires (*.tmp)
try (var stream = Files.list(dir)) {
stream
.filter(path -> path.toString().endsWith(".tmp"))
.forEach(path -> {
try {
Files.delete(path);
System.out.println("Supprimé : " + path.getFileName());
} catch (IOException e) {
System.out.println("Erreur de suppression " + path.getFileName() + " : " + e.getMessage());
}
});
}
Utiliser l’API Stream pour traiter des collections de chemins
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)); // par taille, ordre décroissant
} catch (IOException e) {
return 0;
}
})
.limit(5) // seulement les 5 plus gros
.forEach(System.out::println);
}
4. Exercices pratiques
Recherche et suppression des fichiers antérieurs à une certaine date
Supprimons tous les fichiers qui n’ont pas été modifiés depuis plus d’un an :
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("Supprimé : " + path.getFileName());
} catch (IOException e) {
System.out.println("Erreur de suppression " + path.getFileName() + " : " + e.getMessage());
}
});
}
Renommage en masse des fichiers selon un modèle
Supposons qu’il faille ajouter à chaque fichier .txt le préfixe "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("Renommé : " + path.getFileName() + " -> " + newPath.getFileName());
} catch (IOException e) {
System.out.println("Erreur de renommage " + path.getFileName() + " : " + e.getMessage());
}
});
}
Copie de tous les fichiers de tous les sous-dossiers (récursivement)
Utilisons Files.walk pour parcourir tous les fichiers dans tous les sous-dossiers :
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("Copié : " + path + " -> " + targetPath);
} catch (IOException e) {
System.out.println("Erreur de copie " + path + " : " + e.getMessage());
}
});
}
5. Points importants et subtilités
Gestion des erreurs lors des opérations de masse
Dans les opérations de masse, on rencontre presque toujours des fichiers « problématiques » : pas d’accès, fichier occupé, chemin trop long, fichier déjà existant, etc. Si vous ne gérez pas les exceptions à l’intérieur de la boucle, le programme peut « tomber » dès la première erreur. Utilisez donc toujours try-catch à l’intérieur de forEach pour « avaler » l’erreur d’un fichier mais traiter les autres.
Impact de la récursivité sur les performances et la pile d’appels
Avec Files.walk sur de grands répertoires, on peut obtenir un parcours très long si l’on fait la récursivité manuellement. Heureusement, la méthode elle-même est implémentée efficacement et n’entraîne pas de dépassement de pile. Mais si vous écrivez votre propre fonction récursive pour supprimer/copier des répertoires, soyez prudent : pour des structures extrêmement imbriquées, la pile peut « exploser ».
Travailler avec de grands dossiers
- Utilisez Files.walk avec prudence : si un dossier contient des milliers de fichiers, cela peut prendre du temps et « consommer » beaucoup de mémoire.
- Si vous ne devez traiter que le niveau supérieur — utilisez Files.list.
- Pour rechercher des fichiers par motif, utilisez le filtrage par nom (endsWith, matches, etc.).
Exemple : « Nettoyer un dossier des fichiers temporaires »
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("Supprimé : " + path);
} catch (IOException e) {
System.out.println("Erreur de suppression " + path + " : " + e.getMessage());
}
});
}
Méthodes principales pour les opérations de masse
| Opération | Méthode/approche | Particularités |
|---|---|---|
| Obtenir tous les fichiers | |
walk — récursif, list — uniquement le niveau supérieur |
| Filtrage par extension | |
Peut se combiner avec d’autres filtres |
| Copie d’un groupe de fichiers | |
Vérifiez l’existence du dossier cible |
| Suppression d’un groupe de fichiers | |
Mieux vaut utiliser try-catch pour chaque opération |
| Renommage en masse | |
Renommer — c’est un move avec un nouveau nom |
6. Erreurs courantes lors des opérations de masse
Erreur n° 1 : exceptions non gérées dans forEach.
Problème très courant : si vous n’encapsulez pas chaque opération dans try-catch, le programme « tombera » sur le premier fichier problématique et les autres ne seront pas traités.
Erreur n° 2 : tentative de supprimer/copier un dossier comme un fichier (ou inversement).
Les méthodes Files.delete et Files.copy se comportent différemment avec les fichiers et les dossiers. Ne les confondez pas ! Par exemple, tenter de supprimer un dossier non vide avec la méthode standard provoquera une erreur.
Erreur n° 3 : mauvaise construction du chemin cible lors de la copie.
Si vous construisez le chemin de destination sans tenir compte de la structure (par exemple, sans utiliser relativize), vous pouvez écraser des fichiers ou obtenir une structure incorrecte dans l’archive.
Erreur n° 4 : ouverture d’un trop grand nombre de fichiers simultanément.
Si vous ouvrez des flux de lecture/écriture dans une boucle, n’oubliez pas de les fermer ! Il vaut mieux utiliser try-with-resources.
Erreur n° 5 : parcours récursif sans limite de profondeur.
Avec Files.walk sur des dossiers très grands et profonds, vous pouvez rencontrer des problèmes de performances et de mémoire. Si vous devez limiter la profondeur — utilisez la surcharge avec un paramètre de profondeur : Files.walk(dir, depth).
Erreur n° 6 : ignorer implicitement des fichiers cachés/système.
Si votre application manipule des fichiers utilisateur, vous pouvez toucher par inadvertance des fichiers cachés ou système. Pour les filtrer, utilisez la méthode Files.isHidden(path).
GO TO FULL VERSION