W3docs

Eliminare file in Java

Elimina file e directory in Java con File.delete, Files.delete e Files.deleteIfExists. Guida pratica con esempi.

Tre chiamate semplici, una differenza importante. File.delete() restituisce un boolean per qualsiasi esito, dal successo al permesso negato; Files.delete() lancia eccezioni specifiche per ogni tipo di fallimento; Files.deleteIfExists() è la via di mezzo — boolean solo per la domanda "ho eliminato qualcosa?", eccezioni per i fallimenti reali. Scegliere quella giusta dipende principalmente da quanto ti importa sapere perché l'eliminazione è fallita.

Vengono trattati anche: la rimozione di un albero di directory non vuoto (nessuna delle tre chiamate lo fa da sola), l'opzione di apertura DELETE_ON_CLOSE per i file temporanei, e il pattern sicuro "sposta poi elimina" per sostituire un file in modo atomico.

File.delete() — legacy, restituisce boolean

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

Il limite dell'API legacy, con la stessa forma di mkdir e renameTo: un singolo boolean per ogni esito. La chiamata restituisce false se il file non esisteva, se mancavano i permessi, se il percorso era una directory non vuota, oppure — su Windows — se un altro processo teneva aperto il file. Dal valore di ritorno non puoi distinguere quale dei casi si è verificato.

File.delete() rimuove:

  • Un file normale.
  • Una directory vuota. Le directory non vuote restituiscono false.
  • Un link simbolico stesso (non il target).

Se ti basta che "il file sia sparito entro la fine della chiamata," controlla f.exists() dopo, invece di affidarti al valore di ritorno:

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

Questo pattern è quello che troverai usato al suo posto nella maggior parte dei codebase più datati.

Files.delete(path) — moderno, lancia eccezioni

Il corrispondente in java.nio.file scambia il boolean con eccezioni specifiche:

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)

I tipi di eccezione sono sottoclassi di IOException, quindi un generico catch (IOException e) funziona ancora. La differenza è che la gestione degli errori reale può essere specifica:

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
}

Se "già assente" va bene, usa Files.deleteIfExists invece di catturare NoSuchFileException.

Files.deleteIfExists(path) — la via di mezzo

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

Il boolean qui distingue solo "ho rimosso qualcosa" da "niente da rimuovere." Gli errori reali lanciano comunque un'eccezione. È la chiamata giusta per il codice setUp / tearDown, la pulizia idempotente e i pattern del tipo "elimina questo vecchio marker se c'è":

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

Usa la tabella:

Vuoi…Usa
Boolean legacy, nessuna info sull'erroreFile.delete()
"Elimina o dimmi perché ha fallito"Files.delete(path)
"Elimina se presente; silenzioso se assente"Files.deleteIfExists(path)

Eliminare un albero di directory non vuoto

Nessuno dei deleter a singola chiamata rimuove una directory non vuota. Esistono due pattern standard:

Pattern 1: visita e cancella in ordine inverso. Files.walk produce uno Stream<Path> in ordine arbitrario; ordinare in ordine inverso spinge le foglie in testa, così ogni genitore è vuoto quando ci si arriva.

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

Conciso e la versione usata dalla maggior parte dei codebase moderni. Il compromesso: carica tutti i path in memoria per l'ordinamento. Per directory con milioni di voci, preferisci il pattern 2.

Pattern 2: Files.walkFileTree con un SimpleFileVisitor. Il pattern visitor ti permette di eliminare le foglie alla visita e i genitori in postVisitDirectory, senza bisogno di ordinamento:

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

Stesso risultato, nessun ordinamento in memoria, più righe. L'API visitor è trattata nel capitolo Walk file tree.

Non esiste un rmrf integrato nel JDK. Entrambi i pattern sopra sono i sostituti standard; molti codebase includono un piccolo helper Files.deleteRecursively(root) costruito su uno di essi.

DELETE_ON_CLOSE — file temporanei che si puliscono da soli

Per "ho bisogno di un file solo mentre questo stream è aperto," l'opzione StandardOpenOption.DELETE_ON_CLOSE elimina il file quando lo stream si chiude — anche sul percorso dell'eccezione:

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

Il file viene scollegato dalla directory immediatamente sulla maggior parte dei sistemi Unix (altri processi non possono più vederlo per nome; solo l'handle lo mantiene in vita). È il pattern giusto per i dati temporanei di breve durata — nessun codice try/finally da ricordare.

File.deleteOnExit() è la versione precedente e più debole: mette in coda un'eliminazione da eseguire durante lo spegnimento della JVM. Non viene chiamata con kill -9 o in caso di crash della JVM, quindi può causare perdite. Usa DELETE_ON_CLOSE quando la durata del file è legata a uno stream; usa un try/finally (o try-with-resources intorno a un holder AutoCloseable) quando non lo è.

Una nota sul "rimpiazzo atomico"

Sostituire un file con un altro in modo atomico — in modo che un lettore non veda mai una versione scritta a metà — non è un pattern delete-then-write. L'idioma standard è "scrivi su un file adiacente, poi rinomina atomicamente":

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 scambia i due path in un singolo step del sistema operativo (sui filesystem che lo supportano). Il vecchio data.json viene sostituito; non esiste nessun momento intermedio in cui il file è scritto a metà o mancante.

Un esempio pratico: ogni deleter e una rimozione ricorsiva

Il programma seguente costruisce un piccolo albero, quindi esercita ogni deleter a turno — il boolean legacy, la versione moderna che lancia eccezioni, il middle "if exists", e infine il pattern Files.walk + reverseOrder che rimuove un intero albero. Ogni step stampa cosa è successo.

java— editable, runs on the server

Cosa ricavare dall'esecuzione:

  • Le due chiamate File.delete() su a.txt hanno restituito true poi false. Il secondo false sembra identico a un fallimento per mancanza di permessi — questo è il limite dell'API legacy.
  • Files.delete(sub) ha lanciato DirectoryNotEmptyException e Files.delete(missing) ha lanciato NoSuchFileException. Due sottoclassi specifiche di IOException, due modalità di fallimento distinte — esattamente quello che l'API boolean non riesce a dirti.
  • Files.deleteIfExists(b) ha restituito true la prima volta e false la seconda. Quel secondo false significa solo "non c'era" — un vero fallimento (permesso negato, lock) avrebbe lanciato un'eccezione.
  • Il blocco Files.walk + reverseOrder ha eliminato prima le foglie e poi i genitori. Ogni chiamata Files.delete lungo il percorso ha avuto successo perché, quando il visitatore raggiungeva una directory, i suoi figli erano già stati rimossi.
  • Il file DELETE_ON_CLOSE era sparito nel momento in cui il writer si è chiuso — garantito anche sul percorso dell'eccezione. (Sulla maggior parte dei sistemi Unix viene scollegato dalla directory immediatamente all'apertura, quindi Files.exists potrebbe già riportare false dentro il try; su Windows sopravvive fino alla chiusura dell'handle. In ogni caso, non rimane nulla in seguito.) È il pattern per i file temporanei più pulito nel JDK — nessun shutdown hook, nessun try/finally da ricordare.

Prossimi passi

Questo chiude i capitoli di alto livello sulle "operazioni singole su un file". Il capitolo successivo, Byte Streams in Java, scende di un livello: InputStream e OutputStream, l'astrazione orientata ai byte grezzi su cui si fondano ogni file, socket e pipe in java.io. Molti degli helper usati finora — Files.readString, Files.newBufferedWriter, persino FileReader — sono decorator su queste due interfacce.

Esercitazione

Pratica
`Files.deleteIfExists(path)` restituisce `false` quando…
`Files.deleteIfExists(path)` restituisce `false` quando…
Was this page helpful?