1. Scrittura con sovrascrittura
Quando lavori con i file, è importante capire che «scrivere su un file» non è sempre la stessa cosa. A volte devi sovrascrivere completamente il file (ad esempio, quando salvi un nuovo report), altre volte devi aggiungere nuove informazioni in coda a un file già esistente (per esempio per tenere il log degli eventi dell’applicazione). In alcuni casi devi semplicemente leggere il contenuto del file senza modificarlo.
In Java (soprattutto nel moderno package java.nio.file) la modalità di lavoro con il file è determinata da un insieme di opzioni speciali che passi ai metodi di scrittura, ad esempio a Files.write(). Queste opzioni permettono di indicare esplicitamente se vuoi sovrascrivere il file o aggiungere qualcosa in coda.
Come funziona
Quando chiami Files.write(path, data), Java per impostazione predefinita crea un nuovo file oppure sovrascrive quello esistente. Tutto il vecchio contenuto del file verrà eliminato e al suo posto rimarranno solo i nuovi dati.
Questo comportamento predefinito è una sorta di «hard reset» del file. Se il file conteneva prima 1000 righe e tu ne scrivi una sola, tutto ciò che c’era prima scomparirà senza lasciare traccia.
Esempio: scrivere una stringa su un file
import java.nio.file.*;
import java.io.IOException;
import java.util.List;
public class OverwriteFileExample {
public static void main(String[] args) throws IOException {
Path path = Paths.get("myfile.txt");
List<String> lines = List.of("Ciao, mondo!", "Questa è una nuova voce nel file.");
// Scriviamo le righe nel file (il contenuto precedente sarà eliminato)
Files.write(path, lines);
System.out.println("File sovrascritto con successo!");
}
}
Dopo l’esecuzione di questo codice, nel file myfile.txt rimarranno solo le due righe della lista lines. Tutto ciò che c’era prima scomparirà (senza alcun «rammarico» da parte di Java).
2. Append: aggiungere dati alla fine del file
In alcuni casi (ad esempio tenere un registro eventi, fare logging, accumulare dati) è necessario non eliminare il vecchio contenuto del file, ma aggiungere nuove righe in coda. Nel vecchio API si usava spesso FileWriter con il flag append = true. Nel moderno API tutto è più semplice ed esplicito.
Come si fa
Tutto ciò che serve è passare l’opzione StandardOpenOption.APPEND al metodo Files.write():
import java.nio.file.*;
import java.io.IOException;
import java.util.List;
public class AppendFileExample {
public static void main(String[] args) throws IOException {
Path path = Paths.get("myfile.txt");
List<String> moreLines = List.of("Aggiunta un'altra riga.", "E un'altra ancora!");
// Aggiungiamo righe alla fine del file (il contenuto precedente viene mantenuto)
Files.write(path, moreLines, StandardOpenOption.APPEND);
System.out.println("Righe aggiunte alla fine del file!");
}
}
Punto importante: se il file non esiste, tentando l’append si verificherà un errore: Java non creerà automaticamente il file in modalità APPEND. Per creare il file se non esiste, usa due opzioni insieme:
Files.write(path, moreLines, StandardOpenOption.APPEND, StandardOpenOption.CREATE);
Come appare nella pratica
- Esegui il programma con scrittura (sovrascrittura): nel file ci sono due righe.
- Esegui il programma con append: a queste righe se ne aggiungono di nuove, le vecchie restano al loro posto.
3. Combinazione di opzioni: CREATE, APPEND, TRUNCATE_EXISTING
In Java puoi combinare più opzioni per gestire in modo più flessibile le modalità di apertura del file:
- StandardOpenOption.CREATE — crea il file se non esiste.
- StandardOpenOption.APPEND — aggiunge i dati in coda.
- StandardOpenOption.TRUNCATE_EXISTING — tronca il file a zero byte (svuota) se esiste.
- StandardOpenOption.CREATE_NEW — crea un nuovo file; se esiste già, genera un errore.
Esempio: creare il file se non esiste e aggiungere dati alla fine
Files.write(path, moreLines, StandardOpenOption.CREATE, StandardOpenOption.APPEND);
Esempio: creare il file oppure svuotarlo completamente e scrivere nuovi dati
Files.write(path, lines, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
Tabella: modalità di scrittura principali
| Opzione/i | Comportamento |
|---|---|
| (predefinito) | Creare il file o sovrascrivere quello esistente |
|
Aggiungere in coda; errore se il file non esiste |
|
Aggiungere in coda; creare il file se non esiste |
|
Troncare il file a zero e scrivere nuovi dati |
|
Creare un nuovo file; errore se il file esiste già |
4. Lettura e scrittura di file binari
Finora abbiamo lavorato con le stringhe. Ma talvolta è necessario scrivere o leggere byte «grezzi» (ad esempio immagini, archivi, file PDF). In questo caso utilizza:
- Files.readAllBytes(path) — legge il file in un array di byte.
- Files.write(path, byteArray) — scrive un array di byte su file.
Esempio: copia di un file
import java.nio.file.*;
import java.io.IOException;
public class CopyBinaryFileExample {
public static void main(String[] args) throws IOException {
Path source = Paths.get("logo.png");
Path target = Paths.get("logo_copy.png");
byte[] data = Files.readAllBytes(source);
Files.write(target, data);
System.out.println("File copiato!");
}
}
Esempio: append di dati binari
byte[] moreData = new byte[] {1, 2, 3, 4, 5};
Files.write(path, moreData, StandardOpenOption.APPEND, StandardOpenOption.CREATE);
Attenzione: fare append di dati binari in un file che contiene già dati strutturati (ad esempio un’immagine) in genere porterà a corrompere il file. Usa l’append solo per file testuali o binari appositamente preparati (ad esempio log).
5. Quando servono gli stream (FileInputStream, BufferedReader ecc.)
I metodi Files.readAllBytes(), Files.write() sono comodi per file piccoli e medi, quando è possibile caricare tranquillamente tutto il contenuto in memoria in un colpo solo. Se il file è grande (ad esempio gigabyte), o se vuoi leggerlo riga per riga (per esempio per analizzare log), usa gli stream.
Esempio: lettura di un file riga per riga con BufferedReader
import java.nio.file.*;
import java.io.*;
public class ReadLinesExample {
public static void main(String[] args) throws IOException {
Path path = Paths.get("myfile.txt");
try (BufferedReader reader = Files.newBufferedReader(path)) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println("Riga: " + line);
}
}
}
}
Esempio: scrittura del file riga per riga con BufferedWriter e append
import java.nio.file.*;
import java.io.*;
import java.nio.charset.StandardCharsets;
public class WriteAppendExample {
public static void main(String[] args) throws IOException {
Path path = Paths.get("myfile.txt");
try (BufferedWriter writer = Files.newBufferedWriter(
path,
StandardCharsets.UTF_8,
StandardOpenOption.CREATE,
StandardOpenOption.APPEND)) {
writer.write("Un'altra riga tramite BufferedWriter!");
writer.newLine();
}
}
}
6. Pratica: creiamo un file e vi facciamo append di righe
Mettiamo insieme tutto in un’unica applicazione. Supponiamo di avere un programma che mantiene una lista di attività (todo-list) in un file di testo. Ogni volta che l’utente aggiunge una nuova attività, la aggiungiamo in coda al file.
import java.nio.file.*;
import java.io.IOException;
import java.util.List;
import java.util.Scanner;
public class TodoList {
public static void main(String[] args) throws IOException {
Path path = Paths.get("todo.txt");
Scanner scanner = new Scanner(System.in);
System.out.print("Inserisci una nuova attività: ");
String task = scanner.nextLine();
// Aggiungiamo l'attività in coda al file (creiamo il file se non esiste)
Files.write(path,
List.of(task),
StandardOpenOption.CREATE,
StandardOpenOption.APPEND);
System.out.println("Attività aggiunta!");
}
}
Esegui il programma più volte: ogni attività apparirà nel file su una nuova riga. Ecco fatto il nostro primo todo‑list «eterno»!
7. Gestione degli errori: cosa può andare storto?
Il lavoro con i file è sempre soggetto a errori. Ecco cosa può succedere:
- IOException — eccezione di base per tutti gli errori di I/O. Può verificarsi se il file è occupato da un altro programma, se non ci sono permessi di scrittura/lettura, se il disco è pieno, ecc.
- NoSuchFileException — se cerchi di leggere o fare append su un file che non esiste (e non hai indicato l’opzione CREATE).
- FileAlreadyExistsException — se usi l’opzione CREATE_NEW e il file esiste già.
Raccomandazione: incapsula sempre il lavoro con i file in un blocco try-catch:
try {
// le tue operazioni con i file
} catch (IOException e) {
System.out.println("Errore durante il lavoro con il file: " + e.getMessage());
}
8. Errori tipici con le modalità di apertura dei file
Errore n. 1: hai dimenticato di indicare l’opzione CREATE durante l’append. Se provi a fare append (APPEND) su un file che ancora non esiste, otterrai NoSuchFileException. Aggiungi sempre CREATE se vuoi che il file venga creato automaticamente.
Errore n. 2: sovrascrittura accidentale del file. La chiamata a Files.write(path, data) senza opzioni aggiuntive elimina tutto il vecchio contenuto del file. Se vuoi aggiungere dati invece di eliminare i vecchi, usa APPEND.
Errore n. 3: tentare l’append di dati binari in un file di testo (o viceversa). Se mescoli dati testuali e binari nello stesso file, molto probabilmente otterrai un file illeggibile. Mantieni sempre un unico formato per ciascun file.
Errore n. 4: hai dimenticato di chiudere lo stream. Se usi gli stream (BufferedWriter, BufferedReader), non dimenticare di chiuderli (meglio tramite try-with-resources), altrimenti il file potrebbe rimanere bloccato.
Errore n. 5: eccezioni non gestite. Qualsiasi operazione con i file può lanciare IOException. Se non gestisci l’errore, il programma terminerà in modo anomalo.
GO TO FULL VERSION