1. NIO2: allons plus loin
Nous avons déjà fait connaissance avec NIO2, mais reprenons et approfondissons nos connaissances sur cette bibliothèque utile pour travailler avec les fichiers et les répertoires.
Autrefois, en Java, il n’y avait que la classe File. Elle savait vérifier l’existence d’un fichier, créer et supprimer des fichiers et des dossiers, obtenir la liste des fichiers d’un répertoire. Mais elle présentait de nombreuses limites :
- peu pratique de travailler avec les chemins, surtout lorsqu’il faut tenir compte de différents systèmes d’exploitation (C:\Users\user\file.txt sous Windows et /home/user/file.txt sous Linux) ;
- pas de prise en charge correcte des liens symboliques, des permissions et des attributs de fichiers ;
- des possibilités limitées de parcours de l’arborescence de répertoires ;
- la gestion des erreurs laissait à désirer.
Avec l’arrivée de NIO2 (New Input/Output, version 2) dans Java 7, la vie des développeurs est devenue plus simple et plus agréable. Désormais, vous disposez de :
- la classe Path pour travailler aisément avec les chemins vers les fichiers et les dossiers ;
- la classe Files, qui fournit toutes les opérations de base : lecture, écriture, copie, suppression, obtention d’informations sur les fichiers ;
- l’interface FileVisitor et des méthodes comme Files.walk, qui permettent de parcourir facilement et avec souplesse le système de fichiers.
Pourquoi est-ce important ?
- Multiplateforme : Le même code fonctionne sur Windows, Linux, macOS, sans se soucier des séparateurs (/ ou \).
- Sécurité et simplicité : Plus d’informations sur les erreurs, moins de magie et de mauvaises surprises.
- Puissance : Vous pouvez traiter d’énormes répertoires et même parcourir récursivement avec filtrage et traitement parallèle.
2. Classes principales : Path et Files
Classe Path
Path — c’est une représentation moderne d’un chemin vers un fichier ou un dossier. Il n’indique pas nécessairement un fichier existant — c’est simplement un chemin avec lequel il est pratique de travailler.
Obtenir un Path
import java.nio.file.Path;
import java.nio.file.Paths;
Path path1 = Paths.get("file.txt"); // chemin relatif
Path path2 = Paths.get("/home/user/file.txt"); // chemin absolu
Path path3 = Path.of("mydir", "subdir", "file.txt"); // depuis Java 11+
Fait : Path ne dépend pas du système d’exploitation. Oubliez la concaténation manuelle de chaînes avec / ou \ !
Conversion en chaîne
System.out.println(path1.toString());
Obtenir le répertoire parent et le nom de fichier
Path parent = path1.getParent(); // peut être null pour les chemins relatifs
Path fileName = path1.getFileName(); // uniquement le nom du fichier
Classe Files
Files — c’est un ensemble de méthodes statiques pour toutes les opérations sur les fichiers et les répertoires :
- Vérification d’existence : Files.exists(path)
- Lecture et écriture de fichiers : Files.readAllBytes(path), Files.write(path, bytes)
- Obtention d’informations : Files.size(path), Files.getLastModifiedTime(path)
- Copie, suppression, déplacement : Files.copy, Files.delete, Files.move
Exemples :
import java.nio.file.Files;
import java.nio.file.Path;
Path path = Path.of("file.txt");
if (Files.exists(path)) {
System.out.println("Le fichier existe !");
System.out.println("Taille: " + Files.size(path) + " octets");
System.out.println("Dernière modification: " + Files.getLastModifiedTime(path));
} else {
System.out.println("Fichier introuvable.");
}
3. Parcours du système de fichiers: Files.walk et associés
Le problème de l’ancienne méthode
Dans l’ancien API, pour parcourir tous les fichiers d’un dossier et de ses sous-dossiers, il fallait écrire des fonctions récursives, vérifier manuellement ce qui est fichier ou dossier, et veiller à ne pas tomber dans une récursion infinie. C’était non seulement fastidieux, mais aussi très propice aux erreurs.
Approche moderne : Files.walk
Files.walk(Path start) retourne un Stream<Path> — un flux de tous les fichiers et dossiers à partir du chemin indiqué, y compris tous les sous-répertoires. Désormais, parcourir le système de fichiers revient à travailler avec des streams !
Exemple: afficher tous les fichiers et dossiers
import java.nio.file.*;
try (var paths = Files.walk(Path.of("mydir"))) {
paths.forEach(System.out::println);
}
Ici, tous les chemins seront affichés : fichiers et dossiers, en partant de mydir.
Exemple: uniquement les fichiers (sans dossiers)
try (var paths = Files.walk(Path.of("mydir"))) {
paths.filter(Files::isRegularFile)
.forEach(System.out::println);
}
La méthode Files.isRegularFile(path) renverra true uniquement pour les fichiers ordinaires (ni dossiers, ni liens symboliques).
Exemple: recherche de fichiers par extension
Supposons que nous devions trouver tous les fichiers .txt dans un répertoire et ses sous-répertoires :
try (var paths = Files.walk(Path.of("mydir"))) {
paths.filter(Files::isRegularFile)
.filter(path -> path.toString().endsWith(".txt"))
.forEach(System.out::println);
}
Exemple: calcul de la taille totale de tous les fichiers
long totalSize = 0;
try (var paths = Files.walk(Path.of("mydir"))) {
totalSize = paths.filter(Files::isRegularFile)
.mapToLong(path -> {
try {
return Files.size(path);
} catch (Exception e) {
System.err.println("Erreur de lecture de la taille: " + path);
return 0L;
}
})
.sum();
}
System.out.println("Taille totale des fichiers: " + totalSize + " octets");
Important !
- La méthode Files.walk renvoie un stream qu’il faut fermer (elle implémente AutoCloseable). Utilisez donc try-with-resources !
- Par défaut, la profondeur de parcours est maximale (tous les sous-répertoires). On peut limiter la profondeur : Files.walk(path, maxDepth)
4. Exercices pratiques
Exercice 1: Trouver toutes les images dans un répertoire
Il faut trouver tous les fichiers avec l’extension .jpg, .png, .gif dans le dossier images et afficher leurs noms.
import java.nio.file.*;
import java.util.Set;
Set<String> extensions = Set.of(".jpg", ".png", ".gif");
try (var paths = Files.walk(Path.of("images"))) {
paths.filter(Files::isRegularFile)
.filter(path -> {
String name = path.getFileName().toString().toLowerCase();
return extensions.stream().anyMatch(name::endsWith);
})
.forEach(System.out::println);
}
Exercice 2: Copier tous les fichiers .txt vers un autre dossier
import java.nio.file.*;
Path sourceDir = Path.of("src");
Path destDir = Path.of("dest");
try (var paths = Files.walk(sourceDir)) {
paths.filter(Files::isRegularFile)
.filter(path -> path.toString().endsWith(".txt"))
.forEach(path -> {
try {
Path relative = sourceDir.relativize(path);
Path target = destDir.resolve(relative);
Files.createDirectories(target.getParent());
Files.copy(path, target, StandardCopyOption.REPLACE_EXISTING);
System.out.println("Copié: " + path + " -> " + target);
} catch (Exception e) {
System.err.println("Erreur de copie: " + path);
}
});
}
Ici, nous conservons la structure des sous-répertoires.
5. Points utiles
Avantages de NIO2
Multiplateforme
Path gère lui-même les séparateurs de dossiers. Votre code fonctionnera de la même manière sur Windows, Linux, macOS.
Traitement en flux
Des méthodes comme Files.walk renvoient un flux (Stream<Path>) que l’on peut filtrer, transformer, collecter — tout ce que sait faire l’API Stream.
Traitement de grands répertoires
L’ancien API pouvait « planter » si les fichiers étaient trop nombreux (par exemple, 100 000 photos). NIO2 gère facilement ces cas, car il ne charge pas tout en mémoire d’un coup.
Prise en charge des liens symboliques, attributs, permissions
Vous pouvez savoir si un chemin est un lien symbolique (Files.isSymbolicLink(path)), obtenir les permissions (Files.getPosixFilePermissions(path)), connaître le propriétaire d’un fichier, et bien plus encore.
Comparaison de l’ancien et du nouvel API
| Opération | Ancien API (File) | Nouvel API (Path, Files) |
|---|---|---|
| Vérifier l’existence | |
|
| Obtenir la taille | |
|
| Liste des fichiers d’un dossier | |
|
| Parcours récursif | Récursion manuelle | |
| Copie de fichier | file.renameTo() (peu fiable) | |
| Obtenir l’extension | Analyser la chaîne | |
| Obtenir le parent | |
|
| Gestion des permissions | Presque aucune | |
Caractéristiques importantes
Vérification du type de fichier
- Files.isRegularFile(path) — fichier ordinaire
- Files.isDirectory(path) — dossier
- Files.isSymbolicLink(path) — lien symbolique
Travail avec de grands répertoires
- Évitez de collecter tous les chemins dans une liste : travaillez avec des flux (Stream<Path>) et traitez-les au fil de l’eau.
- Une fois le travail terminé, le flux doit impérativement être fermé (try-with-resources).
Exceptions
- Presque toutes les méthodes peuvent lancer une IOException — n’oubliez pas de gérer les erreurs (ou de les propager).
Limiter la profondeur de parcours
try (var paths = Files.walk(Path.of("mydir"), 2)) { // seulement 2 niveaux
// ...
}
6. Erreurs courantes avec NIO2
Erreur n° 1: vous avez oublié de fermer le stream walk. Si vous n’utilisez pas try-with-resources, vous risquez une fuite de ressources — le flux du système de fichiers restera ouvert. Utilisez toujours la construction try (var paths = Files.walk(...)) { ... }.
Erreur n° 2: vous n’avez pas vérifié que le chemin est un répertoire. Si vous passez à Files.walk un chemin vers un fichier et non vers un dossier, vous pouvez obtenir un comportement inattendu ou une erreur.
Erreur n° 3: exceptions non gérées. Pratiquement toutes les méthodes de NIO2 peuvent lever une IOException. Ne laissez pas ces erreurs sans traitement — affichez au minimum un message à l’utilisateur ou journalisez-les.
Erreur n° 4: confusion avec les séparateurs de chemin. Si vous concaténez manuellement des chemins avec / ou \, ce n’est pas la bonne approche ! Utilisez Path.of(...) ou resolve(...) — elles s’occupent de tout selon l’OS.
Erreur n° 5: tenter de lire un immense répertoire « en mémoire ». Ne collectez pas tous les chemins dans une liste si les fichiers sont très nombreux — travaillez avec des flux (Stream<Path>) et traitez-les au fur et à mesure.
Erreur n° 6: oublier l’aspect multiplateforme. N’inscrivez pas en dur des chemins absolus au style Windows ou Unix. Utilisez Path et des opérations comme resolve/relativize — elles feront le bon choix sur tout OS.
GO TO FULL VERSION