W3docs

Dateien in Java löschen

Dateien und Verzeichnisse in Java löschen mit File.delete, Files.delete und Files.deleteIfExists – Unterschiede und Einsatzbereiche erklärt.

Drei kleine Methoden, ein großer Unterschied. File.delete() gibt einen boolean zurück – ob die Datei gelöscht wurde, nicht vorhanden war, oder der Zugriff verweigert wurde; Files.delete() wirft spezifische Exceptions für jeden Fehlerfall; Files.deleteIfExists() ist die mittlere Option – boolean nur für die Frage „Habe ich etwas gelöscht?", Exceptions für echte Fehler. Die richtige Methode zu wählen hängt davon ab, wie sehr Sie wissen möchten, warum das Löschen fehlgeschlagen ist.

Ebenfalls behandelt: das Entfernen eines nicht leeren Verzeichnisbaums (keine der drei Methoden leistet das allein), die DELETE_ON_CLOSE-Öffnungsoption für temporäre Dateien sowie das sichere „Verschieben-dann-Löschen"-Muster zum atomaren Ersetzen einer Datei.

File.delete() — veraltet, gibt boolean zurück

File f = new File("notes.txt");
boolean ok = f.delete();           // true on success
                                   // false on every failure: missing, locked, perms, non-empty dir

Die bekannte Schwäche der Legacy-API, vergleichbar mit mkdir und renameTo: ein einzelner boolean für jedes Ergebnis. Der Aufruf gibt false zurück, wenn die Datei nicht existierte, wenn keine Berechtigung vorhanden war, wenn der Pfad ein nicht leeres Verzeichnis war, oder – unter Windows – wenn ein anderer Prozess die Datei geöffnet hatte. Aus dem Rückgabewert lässt sich nicht ableiten, was genau schiefgelaufen ist.

File.delete() entfernt:

  • Eine reguläre Datei.
  • Ein leeres Verzeichnis. Nicht leere Verzeichnisse führen zu false.
  • Einen symbolischen Link selbst (nicht das Ziel).

Wenn Sie nur wissen möchten, ob die Datei am Ende des Aufrufs verschwunden ist, prüfen Sie danach f.exists() statt sich auf den Rückgabewert zu verlassen:

f.delete();
if (f.exists()) throw new IOException("could not delete " + f);

Dieses Muster findet sich in den meisten älteren Codebasen als Ersatz dafür.

Files.delete(path) — modern, wirft Exceptions

Das java.nio.file-Gegenstück tauscht den boolean gegen spezifische Exceptions:

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)

Die Exception-Typen sind Unterklassen von IOException, sodass ein breites catch (IOException e) weiterhin funktioniert. Der Unterschied liegt darin, dass eine echte Fehlerbehandlung gezielt sein kann:

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
}

Wenn „bereits verschwunden" in Ordnung ist, verwenden Sie Files.deleteIfExists statt NoSuchFileException abzufangen.

Files.deleteIfExists(path) — die mittlere Option

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 failures

Der boolean hier unterscheidet nur zwischen „Ich habe etwas entfernt" und „Nichts zu entfernen". Echte Fehler werfen weiterhin Exceptions. Das ist die Methode für setUp- / tearDown-Code, idempotente Bereinigungen und das Muster „Alten Marker löschen, falls vorhanden":

Files.deleteIfExists(Path.of("lock"));     // safe whether the lock was there or not

Verwenden Sie die folgende Tabelle zur Orientierung:

Sie möchten…Verwenden Sie
Legacy-boolean ohne FehlerinformationenFile.delete()
„Löschen oder sagen, warum es fehlschlug"Files.delete(path)
„Löschen wenn vorhanden; still wenn nicht"Files.deleteIfExists(path)

Nicht leeren Verzeichnisbaum löschen

Keine der Einzelaufruf-Methoden entfernt ein nicht leeres Verzeichnis. Es gibt zwei Standardmuster:

Muster 1: Durchlaufen und in umgekehrter Reihenfolge löschen. Files.walk liefert einen Stream<Path> in beliebiger Reihenfolge; das umgekehrte Sortieren schiebt Blätter nach vorne, sodass jedes übergeordnete Verzeichnis leer ist, wenn Sie es erreichen.

try (Stream<Path> walk = Files.walk(root)) {
  walk.sorted(Comparator.reverseOrder())
      .forEach(p -> {
        try { Files.delete(p); } catch (IOException e) { throw new UncheckedIOException(e); }
      });
}

Kompakt und die Version, die der meiste Code heute verwendet. Der Kompromiss: Alle Pfade werden für die Sortierung in den Speicher geladen. Bei Verzeichnissen mit Millionen von Einträgen ist Muster 2 vorzuziehen.

Muster 2: Files.walkFileTree mit einem SimpleFileVisitor. Das Visitor-Muster ermöglicht das Löschen von Blättern beim Besuch und von Elternverzeichnissen in postVisitDirectory, ohne Sortierung:

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;
  }
});

Gleiches Ergebnis, keine In-Memory-Sortierung, mehr Codezeilen. Die Visitor-API wird im Kapitel Dateibaum durchlaufen behandelt.

Es gibt kein eingebautes rmrf im JDK. Beide Muster oben sind die Standardalternativen; viele Codebasen enthalten einen kleinen Files.deleteRecursively(root)-Helfer, der auf einem der beiden basiert.

DELETE_ON_CLOSE — temporäre Dateien, die sich selbst bereinigen

Für den Fall „Ich brauche eine Datei nur, solange dieser Stream offen ist", löscht die Option StandardOpenOption.DELETE_ON_CLOSE die Datei beim Schließen des Streams – auch im Fehlerfall:

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 here

Auf den meisten Unix-Systemen wird die Datei sofort beim Öffnen aus dem Verzeichnis ausgehängt (andere Prozesse können sie nicht mehr über den Namen finden; nur das Handle hält sie am Leben). Das ist das richtige Muster für kurzlebige temporäre Daten – kein try/finally-Gerüst, das Sie sich merken müssen.

File.deleteOnExit() ist die ältere, schwächere Variante: Sie reiht ein Löschen in die JVM-Shutdown-Sequenz ein. Es wird nicht bei kill -9 oder einem JVM-Absturz ausgeführt, sodass Dateien verloren gehen können. Verwenden Sie DELETE_ON_CLOSE, wenn die Lebensdauer der Datei an einen Stream gebunden ist; verwenden Sie try/finally (oder try-with-resources um einen AutoCloseable-Halter), wenn sie es nicht ist.

Hinweis zu „atomarem Ersetzen"

Eine Datei durch eine andere atomar zu ersetzen – sodass ein Leser nie eine halb geschriebene Version sieht – ist kein Muster aus Löschen-dann-Schreiben. Das Standardidiom lautet „In eine Hilfsdatei schreiben, dann atomar umbenennen":

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 tauscht die beiden Pfade in einem einzigen Betriebssystemschritt (auf Dateisystemen, die dies unterstützen). Die alte data.json wird ersetzt; es gibt keinen Zwischenmoment, in dem die Datei halb geschrieben oder nicht vorhanden ist.

Praxisbeispiel: jede Lösch-Methode und ein rekursives Aufräumen

Das folgende Programm baut einen kleinen Verzeichnisbaum auf und demonstriert dann jede Lösch-Methode der Reihe nach – den veralteten boolean, die moderne werfende Variante, die „falls vorhanden"-Mitte und schließlich das Files.walk + reverseOrder-Muster zum Entfernen eines ganzen Baums. Jeder Schritt gibt aus, was passiert ist.

java— editable, runs on the server

Was aus dem Ausführungsergebnis hervorzuheben ist:

  • Die beiden File.delete()-Aufrufe auf a.txt gaben true und dann false zurück. Das zweite false sieht identisch aus wie ein Berechtigungsfehler – das ist die Legacy-Lücke.
  • Files.delete(sub) warf DirectoryNotEmptyException und Files.delete(missing) warf NoSuchFileException. Zwei spezifische Unterklassen von IOException, zwei unterschiedliche Fehlermodi – genau das, was die boolean-API nicht mitteilen kann.
  • Files.deleteIfExists(b) gab beim ersten Aufruf true und beim zweiten false zurück. Dieses zweite false bedeutet nur „war nicht vorhanden" – ein echter Fehler (Zugriff verweigert, Sperre) hätte eine Exception geworfen.
  • Der Files.walk + reverseOrder-Block löschte zuerst Blätter und zuletzt Elternverzeichnisse. Jeder Files.delete-Aufruf entlang des Wegs war erfolgreich, weil zu dem Zeitpunkt, als der Visitor ein Verzeichnis erreichte, seine Kinder bereits entfernt worden waren.
  • Die DELETE_ON_CLOSE-Datei war verschwunden, sobald der Writer geschlossen wurde – garantiert auch im Fehlerfall. (Auf den meisten Unix-Systemen wird sie beim Öffnen sofort aus dem Verzeichnis ausgehängt, sodass Files.exists innerhalb des try-Blocks bereits false melden kann; unter Windows überlebt sie bis zum Schließen des Handles. So oder so bleibt nichts zurück.) Das ist das sauberste Scratch-Datei-Muster im JDK – kein Shutdown-Hook, kein try/finally, das man sich merken muss.

Wie es weitergeht

Damit sind die übergeordneten Kapitel „Eine Aktion an einer Datei" abgeschlossen. Das nächste Kapitel, Byte-Streams in Java, geht eine Ebene tiefer: InputStream und OutputStream, die byteorientierte Abstraktion, auf der jede Datei, jeder Socket und jede Pipe in java.io aufbaut. Viele der bisher verwendeten Hilfsmethoden – Files.readString, Files.newBufferedWriter, sogar FileReader – sind Dekoratoren über diesen beiden Schnittstellen.

Übungen

Übung
`Files.deleteIfExists(path)` gibt `false` zurück, wenn…
`Files.deleteIfExists(path)` gibt `false` zurück, wenn…
Was this page helpful?