Creare file in Java
Crea file e directory in Java con File.createNewFile, Files.createFile e Files.createDirectories.
"Crea un nuovo file vuoto" e "crea questa struttura di directory" sono due delle operazioni più elementari che il file system offre, eppure Java espone quattro modi sovrapposti per eseguire ciascuna. Le differenze contano — la differenza tra "fallisce se il file esiste" e "sovrascrive silenziosamente," tra "mkdir" e "mkdir -p," tra un metodo legacy che restituisce un boolean e uno moderno che lancia un'eccezione.
Questo capitolo tratta i quattro metodi di creazione:
File.createNewFile()— creazione di file legacy.File.mkdir()/File.mkdirs()— creazione di directory legacy.Files.createFile(path)— "crea o fallisce" atomico moderno.Files.createDirectory(path)/Files.createDirectories(path)— creazione di directory moderna.
Oltre agli helper per file temporanei (Files.createTempFile, Files.createTempDirectory) e ai flag OpenOption che permettono agli scrittori di creare file implicitamente.
File.createNewFile() — legacy, restituisce un boolean
File f = new File("data/users.txt");
boolean created = f.createNewFile(); // true if it actually created the file
// false if it already existed
// throws IOException if the parent dir is missingIl contratto è check-and-create atomico: il sistema operativo garantisce che nessun altro processo possa creare lo stesso percorso tra il controllo dell'esistenza e la creazione. Questo rende createNewFile un lock primitivo in alcuni idiomi "PID file" legacy — if (!f.createNewFile()) throw new IllegalStateException("already running");
Cosa non fa:
- Non crea le directory padre mancanti.
new File("does/not/exist/file.txt").createNewFile()lanciaIOException. - Restituisce
false(non lancia eccezione) quando il file esiste già.
Se hai solo bisogno che il file esista alla fine della chiamata, il valore restituito false va bene. Se hai bisogno che il file sia nuovo di zecca (semantica di lock), false è il segnale per intraprendere un percorso diverso.
File.mkdir() e File.mkdirs()
new File("logs").mkdir(); // creates "logs" — fails if "." has no perms or parent missing
new File("a/b/c").mkdirs(); // creates "a", "a/b", and "a/b/c" — like `mkdir -p`Entrambi restituiscono boolean, entrambi perdono informazioni sul perché si è verificato un fallimento. mkdir fallisce se manca un padre; mkdirs no. Entrambi hanno successo (restituiscono true) solo se la directory è stata appena creata — se esiste già, restituiscono false. Combinato con il problema "nessuna informazione sull'errore", questo è il tipo di API legacy che viene avvolto in un helper:
File dir = new File("data");
if (!dir.exists() && !dir.mkdirs()) throw new IOException("cannot create " + dir);Il moderno Files.createDirectories(path) è la sostituzione in una riga sola.
Files.createFile(path) — moderno, lancia eccezioni
Files.createFile è l'omologo java.nio.file di File.createNewFile() con una differenza importante: lancia un'eccezione invece di restituire un boolean.
Path p = Path.of("data/users.txt");
Files.createFile(p); // creates an empty regular file
// throws FileAlreadyExistsException if it exists
// throws NoSuchFileException if the parent is missingFileAlreadyExistsException è quella che si cattura con catch se il risultato sull'esistenza è importante; NoSuchFileException è quella che si cattura (o si previene con createDirectories) se il padre potrebbe non essere presente. I tipi di eccezione sono sottoclassi specifiche di IOException, quindi un generico catch (IOException e) funziona comunque.
Puoi passare argomenti FileAttribute per impostare i permessi POSIX al momento della creazione su Unix — l'uso più comune è assicurarsi che i file segreti (chiavi private, token) vengano creati con 0600:
import static java.nio.file.attribute.PosixFilePermissions.*;
var attr = asFileAttribute(fromString("rw-------"));
Files.createFile(Path.of("/tmp/secret"), attr); // born with 0600 permissions, atomically(Quella chiamata lancia UnsupportedOperationException su Windows, che non ha un modello di permessi POSIX — proteggi con un controllo della piattaforma se hai come target entrambe le piattaforme.)
Files.createDirectory versus Files.createDirectories
La stessa differenza tra mkdir e mkdir -p, ma basata sulle eccezioni:
Files.createDirectory(Path.of("logs")); // one level deep; parent must exist
Files.createDirectories(Path.of("a/b/c")); // creates every missing ancestorcreateDirectory lancia FileAlreadyExistsException se il target esiste già e non è una directory; se è già una directory, lancia comunque (non quello che di solito si vuole).
createDirectories è la scelta più comoda: non fa nulla se tutte le directory sono già presenti, e crea quelle mancanti altrimenti. Non lancia eccezioni se il percorso esiste già come directory. Questo lo rende idempotente — sicuro da chiamare all'avvio senza un controllo exists().
File e directory temporanei
Per i test, lo spazio di lavoro temporaneo e "ho bisogno di un posto sicuro dove mettere questo per qualche minuto," il JDK fornisce Files.createTempFile e Files.createTempDirectory:
Path scratch = Files.createTempFile("session-", ".log"); // /tmp/session-3829387.log
Path workdir = Files.createTempDirectory("export-"); // /tmp/export-1827392Entrambi scelgono un nome univoco nella directory temporanea del sistema, entrambi restituiscono un Path alla nuova voce, ed entrambi creano la voce con permessi restrittivi su Unix. Il prefisso e il suffisso sono suggerimenti ai quali il JDK aggiunge un valore univoco — non puoi scegliere il nome esatto (questo è il punto: un'altra chiamata non può prevederlo e sovrascrivere il tuo).
I file temporanei non vengono eliminati automaticamente. Devi:
- Chiamare
Files.deleteIfExists(path)quando hai finito; oppure - Chiamare
path.toFile().deleteOnExit()per pianificare un'eliminazione alla chiusura della JVM (annullata da kill forzati); oppure - Aprire il file con
StandardOpenOption.DELETE_ON_CLOSEse ne hai bisogno solo mentre uno stream è aperto.
Gli scrittori creano file implicitamente
La maggior parte delle volte non hai bisogno di una chiamata "crea file" — uno scrittore ne crea uno per te. Files.newBufferedWriter, Files.write e Files.writeString accettano tutti varargs OpenOption... che decidono cosa succede quando il file esiste o non esiste:
import static java.nio.file.StandardOpenOption.*;
Files.writeString(path, "hello\n", CREATE, WRITE, TRUNCATE_EXISTING);
Files.writeString(path, "more\n", CREATE, WRITE, APPEND);
Files.writeString(path, "new\n", CREATE_NEW); // fails if file existsCREATE— crea se mancante, altrimenti apri quello esistente.CREATE_NEW— crea, lanciaFileAlreadyExistsExceptionse esiste. La stessa semantica diFiles.createFile.TRUNCATE_EXISTING— svuota il contenuto del file all'apertura (il default perwriteStringquando non si aggiunge in coda).APPEND— scrivi alla fine senza troncare.
Il default per Files.writeString (senza opzioni) è CREATE, WRITE, TRUNCATE_EXISTING — ovvero "crea o sovrascrivi." Files.newBufferedWriter usa gli stessi default. Se vuoi la semantica di append, devi specificarla esplicitamente.
Un esempio pratico: tutti i metodi di creazione a confronto
Il programma seguente costruisce un piccolo albero da zero nella directory temporanea del sistema usando entrambe le API e diverse opzioni di apertura. Ogni passo stampa cosa è cambiato; l'ultimo blocco mostra cosa succede quando si riesegue un'operazione su un percorso che esiste già.
Cosa ricavare dall'esecuzione:
- Il primo
legacy.createNewFile()ha restituitotrue(creato); il secondo ha restituitofalse(già esistente). Ilbooleannon ti dice cosa è successo — devi ricordare la convenzione. deep.mkdirs()ha avuto successo una volta e ha restituitofalsela seconda. Quelfalsesembra identico a "permesso negato" o "padre mancante" — esattamente il problema della mancanza di informazioni sull'errore cheFilesrisolve.Files.createFilesu un percorso esistente ha lanciatoFileAlreadyExistsException. Il tipo di eccezione è specifico, quindi un vero handler può distinguere "già presente" da "permesso negato" senza analizzare stringhe.Files.createDirectorieschiamato due volte di fila non ha fatto nulla di dannoso la seconda volta. È questa proprietà che lo rende la scelta giusta nel codice di avvio: nessuna guardia, basta chiamarlo.Files.writeString(log, "line 1\n")ha creato il file perchéCREATEè nelle opzioni predefinite. Le seconda e terza chiamata hanno usatoAPPENDesplicitamente, e il file ha accumulato tre righe. La quarta chiamata ha usatoCREATE_NEWe si è rifiutata di sovrascrivere. I default sono progettati per il caso "sovrascrivi con nuovo contenuto"; si opta esplicitamente per l'append.Files.createTempFile(root, "scratch-", ".tmp")ha prodotto un nome comescratch-1827392.tmp— il tuo prefisso e suffisso, più un segmento univoco che la JVM sceglie in modo che due chiamate concorrenti non si scontrino mai.- La pulizia percorre
rootin ordine inverso in modo che i file e le directory figlio scompaiano prima dei loro genitori.Files.deletesi rifiuta di eliminare una directory non vuota; quell'ordinamento è il modo in cui viene costruito unrm -rfmanuale.
Prossimi passi
Puoi creare file; il prossimo capitolo, Leggere file in Java, li legge — prima con i metodi moderni in una riga (Files.readString, Files.readAllLines, Files.lines), poi con il classico stack decorator FileReader / BufferedReader / Scanner in modo che il codice più vecchio nei capitoli successivi abbia una base su cui poggiare.