Supprimer des fichiers en Java
Supprimez fichiers et répertoires en Java avec File.delete, Files.delete et Files.deleteIfExists.
Trois petits appels, une grande différence. File.delete() renvoie un boolean pour tout, du succès au refus de permission ; Files.delete() lève des exceptions spécifiques pour chaque échec ; Files.deleteIfExists() est l'option intermédiaire — boolean pour la question « ai-je supprimé quelque chose ? » uniquement, exceptions pour les vraies erreurs. Choisir le bon dépend principalement de l'importance que vous accordez à savoir pourquoi la suppression a échoué.
Sont également couverts : la suppression d'une arborescence de répertoires non vide (aucun des trois appels ne le fait seul), l'option d'ouverture DELETE_ON_CLOSE pour les fichiers temporaires, et le motif sûr « déplacer puis supprimer » pour remplacer un fichier de façon atomique.
File.delete() — héritage, renvoie un boolean
File f = new File("notes.txt");
boolean ok = f.delete(); // true on success
// false on every failure: missing, locked, perms, non-empty dirLa lacune de l'API historique, même forme que mkdir et renameTo : un seul boolean pour chaque résultat. L'appel renvoie false si le fichier n'existait pas, si vous n'aviez pas la permission, si le chemin était un répertoire non vide, ou — sous Windows — si un autre processus avait le fichier ouvert. Vous ne pouvez pas distinguer ces cas à partir de la valeur de retour.
File.delete() supprime :
- Un fichier ordinaire.
- Un répertoire vide. Les répertoires non vides renvoient
false. - Un lien symbolique lui-même (et non sa cible).
Si vous avez seulement besoin que « le fichier soit absent à la fin de l'appel », vérifiez f.exists() ensuite plutôt que de vous fier au retour :
f.delete();
if (f.exists()) throw new IOException("could not delete " + f);C'est le motif que vous trouverez en remplacement dans la plupart des bases de code anciennes.
Files.delete(path) — moderne, lève des exceptions
L'équivalent java.nio.file troque le boolean contre des exceptions spécifiques :
Path p = Path.of("notes.txt");
Files.delete(p);
// throws NoSuchFileException — the file didn't exist
// throws DirectoryNotEmptyException — path was a non-empty directory
// throws AccessDeniedException — permission denied
// throws IOException — anything else (locked, OS error, network FS hiccup)Les types d'exceptions sont des sous-classes de IOException, donc un catch (IOException e) large fonctionne toujours. La différence est qu'une vraie gestion des erreurs peut être spécifique :
try {
Files.delete(path);
} catch (NoSuchFileException e) {
// already gone — not an error in the "delete if there" pattern
} catch (DirectoryNotEmptyException e) {
// need a recursive delete; handled below
}Si « déjà absent » est acceptable, utilisez Files.deleteIfExists plutôt que d'attraper NoSuchFileException.
Files.deleteIfExists(path) — l'option intermédiaire
boolean deleted = Files.deleteIfExists(path);
// returns true — the file existed and was deleted
// returns false — the file did not exist
// throws DirectoryNotEmptyException, AccessDeniedException, etc. for real failuresLe boolean ici distingue seulement « j'ai supprimé quelque chose » de « rien à supprimer ». Les vraies erreurs lèvent quand même une exception. C'est l'appel à utiliser pour le code setUp / tearDown, le nettoyage idempotent, et les motifs « supprimer cet ancien marqueur s'il est là » :
Files.deleteIfExists(Path.of("lock")); // safe whether the lock was there or notUtilisez le tableau :
| Vous voulez… | Utilisez |
|---|---|
| Boolean hérité, sans info d'erreur | File.delete() |
| « Supprimer ou me dire pourquoi ça a échoué » | Files.delete(path) |
| « Supprimer si présent ; silencieux si absent » | Files.deleteIfExists(path) |
Supprimer une arborescence de répertoires non vide
Aucun des appels de suppression simple ne supprime un répertoire non vide. Il existe deux motifs standard :
Motif 1 : parcourir et supprimer en ordre inverse. Files.walk produit un Stream<Path> dans un ordre arbitraire ; trier en ordre inverse pousse les feuilles au début, de sorte que chaque parent est vide au moment où on l'atteint.
try (Stream<Path> walk = Files.walk(root)) {
walk.sorted(Comparator.reverseOrder())
.forEach(p -> {
try { Files.delete(p); } catch (IOException e) { throw new UncheckedIOException(e); }
});
}Concis et c'est la version que la plupart des codes utilisent aujourd'hui. Le compromis : il charge tous les chemins en mémoire pour le tri. Pour les répertoires avec des millions d'entrées, préférez le motif 2.
Motif 2 : Files.walkFileTree avec un SimpleFileVisitor. Le motif visiteur permet de supprimer les feuilles à la visite et les parents dans postVisitDirectory, sans tri requis :
Files.walkFileTree(root, new SimpleFileVisitor<Path>() {
@Override public FileVisitResult visitFile(Path file, BasicFileAttributes a) throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
});Même résultat, pas de tri en mémoire, plus de lignes. L'API visiteur est abordée dans le chapitre Parcourir une arborescence de fichiers.
Il n'existe pas de rmrf intégré dans le JDK. Les deux motifs ci-dessus sont les substituts standard ; de nombreuses bases de code embarquent un petit helper Files.deleteRecursively(root) au-dessus de l'un d'eux.
DELETE_ON_CLOSE — fichiers temporaires qui se nettoient eux-mêmes
Pour « j'ai besoin d'un fichier seulement pendant que ce flux est ouvert », l'option StandardOpenOption.DELETE_ON_CLOSE supprime le fichier à la fermeture du flux — même sur le chemin d'exception :
Path scratch = Files.createTempFile("work-", ".tmp");
try (BufferedWriter w = Files.newBufferedWriter(scratch,
StandardOpenOption.WRITE, StandardOpenOption.DELETE_ON_CLOSE)) {
// ... write to and read from scratch ...
} // scratch is gone after this brace, regardless of how we got hereLe fichier est dissocié du répertoire immédiatement sur la plupart des systèmes Unix (d'autres processus ne peuvent plus le voir par son nom ; seul le handle le maintient en vie). C'est le bon motif pour les données temporaires de courte durée — pas de plomberie try/finally à mémoriser.
File.deleteOnExit() est la version plus ancienne et plus faible : elle met en file d'attente une suppression à exécuter lors de l'arrêt de la JVM. Elle n'est pas appelée lors d'un kill -9 ou d'un crash de la JVM, donc elle provoque des fuites. Utilisez DELETE_ON_CLOSE quand la durée de vie du fichier est liée à un flux ; utilisez try/finally (ou try-with-resources autour d'un holder AutoCloseable) quand ce n'est pas le cas.
Note sur le « remplacement atomique »
Remplacer un fichier par un autre de façon atomique — pour qu'un lecteur ne voie jamais une version à moitié écrite — n'est pas un motif supprimer-puis-écrire. L'idiome standard est « écrire dans un fichier voisin, puis renommer atomiquement » :
Path target = Path.of("data.json");
Path tmp = target.resolveSibling("data.json.tmp");
Files.writeString(tmp, payload);
Files.move(tmp, target, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);ATOMIC_MOVE échange les deux chemins en une seule étape OS (sur les systèmes de fichiers qui le supportent). L'ancien data.json est remplacé ; il n'existe aucun instant intermédiaire où le fichier est à moitié écrit ou absent.
Exemple complet : chaque suppresseur et une suppression récursive
Le programme ci-dessous construit une petite arborescence, puis utilise chaque suppresseur à son tour — le boolean hérité, la version moderne qui lève des exceptions, l'option intermédiaire « si existe », et enfin le motif Files.walk + reverseOrder qui supprime toute une arborescence. Chaque étape affiche ce qui s'est passé.
Ce qu'il faut retenir de l'exécution :
- Les deux appels
File.delete()sura.txtont renvoyétruepuisfalse. Le secondfalseest identique à un échec de permission — c'est la lacune de l'API historique. Files.delete(sub)a levéDirectoryNotEmptyExceptionetFiles.delete(missing)a levéNoSuchFileException. Deux sous-classes spécifiques deIOException, deux modes d'échec distincts — exactement ce que l'API boolean ne peut pas vous dire.Files.deleteIfExists(b)a renvoyétruela première fois etfalsela seconde. Ce secondfalsesignifie uniquement « n'était pas là » — une vraie erreur (permission refusée, verrou) aurait levé une exception.- Le bloc
Files.walk + reverseOrdera supprimé les feuilles en premier et les parents en dernier. Chaque appelFiles.deleteen chemin a réussi car, au moment où le visiteur atteignait un répertoire, ses enfants avaient déjà été supprimés. - Le fichier
DELETE_ON_CLOSEétait absent dès la fermeture du writer — garanti même sur le chemin d'exception. (Sur la plupart des systèmes Unix il est dissocié du répertoire immédiatement à l'ouverture, doncFiles.existspeut déjà renvoyerfalseà l'intérieur dutry; sous Windows il survit jusqu'à la fermeture du handle. Dans tous les cas, rien n'est laissé derrière.) C'est le motif de fichier temporaire le plus propre du JDK — pas de hook d'arrêt, pas detry/finallyà mémoriser.
Et ensuite
Cela clôt les chapitres de haut niveau « faire une chose à un fichier ». Le prochain chapitre, Les flux d'octets en Java, descend d'un niveau : InputStream et OutputStream, l'abstraction orientée octets bruts sur laquelle reposent tous les fichiers, sockets et pipes de java.io. Beaucoup des helpers que vous avez utilisés jusqu'ici — Files.readString, Files.newBufferedWriter, même FileReader — sont des décorateurs par-dessus ces deux interfaces.