File I/O in Java: guida completa per leggere e scrivere file

In questo tutorial vediamo in modo pratico come funziona il File I/O in Java: dalle classi di base (File, FileReader, FileWriter, stream binari) fino alla libreria moderna NIO.2, con esempi completi e pronti da copiare.
1. Cos’è il File I/O in Java e perché è importante
Con questa tecnologia (Input/Output su file) intendiamo tutte le operazioni di lettura e scrittura su file presenti nel file system: file di testo, file binari, log, CSV, ecc. Pertanto, è fondamentale perché:
- Permette di salvare dati in modo persistente (oltre la durata del programma).
- Consente di scambiare informazioni con altri sistemi tramite file (CSV, JSON, XML).
- È usato in log, configurazioni, report, esportazioni/importazioni dati.
Java offre due approcci principali: le API di I/O tradizionali (java.io) e le API moderne basate su NIO.2 (java.nio.file). In questo articolo li vediamo entrambi.
2. Classi principali per il File I/O in Java
2.1 java.io.File
java.io.File rappresenta un percorso nel file system (file o directory). Non legge o scrive dati, tuttavia permette di:
- Verificare se un file esiste.
- Ottenere dimensione, percorso assoluto, permessi.
- Creare o cancellare file e cartelle.
File file = new File("dati.txt");
System.out.println("Esiste? " + file.exists());
System.out.println("Percorso assoluto: " + file.getAbsolutePath());
2.2 FileReader e FileWriter (testo)
FileReader e FileWriter sono stream orientati ai caratteri, usati per leggere e scrivere testi (tipicamente file .txt, .csv, .log se sono testuali). In particolare, sono adatti per file di sola testo.
FileReader: legge caratteri da un file di testo.FileWriter: scrive caratteri su un file di testo.
2.3 BufferedReader e BufferedWriter
BufferedReader e BufferedWriter avvolgono rispettivamente Reader e Writer per rendere le operazioni più efficienti (usano un buffer interno) e più comode (ad esempio, lettura riga per riga).
BufferedReaderfornisce il metodoreadLine()per leggere una riga alla volta.BufferedWriteroffre il metodonewLine()per aggiungere terminatori di riga in modo portabile.
2.4 FileInputStream e FileOutputStream (binario)
FileInputStream e FileOutputStream sono stream orientati ai byte, pensati per leggere e scrivere file binari (immagini, PDF, file compressi, ecc.). Inoltre, sono essenziali quando si gestiscono dati non testuali.
FileInputStream: legge byte da un file.FileOutputStream: scrive byte in un file.
3. Leggere un file di testo con FileReader e BufferedReader (try-with-resources)
Usiamo try-with-resources per aprire il file e chiuderlo automaticamente. In altre parole, leggeremo il file riga per riga garantendo la corretta gestione delle risorse.
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class LetturaFileTesto {
public static void main(String[] args) {
String percorso = "input.txt";
try (BufferedReader reader = new BufferedReader(new FileReader(percorso))) {
String linea;
while ((linea = reader.readLine()) != null) {
System.out.println(linea);
}
} catch (IOException e) {
System.err.println("Errore durante la lettura del file: " + e.getMessage());
e.printStackTrace();
}
}
}
La sintassi try (...) garantisce che il BufferedReader venga chiuso anche in caso di eccezione. Quindi, è una pratica consigliata per evitare perdite di risorse.
4. Scrivere su un file di testo con FileWriter e BufferedWriter
Nel prossimo esempio scriviamo alcune righe in un nuovo file di testo oppure sovrascriviamo quello esistente. Di conseguenza, il contenuto precedente verrà eliminato.
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
public class ScritturaFileTesto {
public static void main(String[] args) {
String percorso = "output.txt";
try (BufferedWriter writer = new BufferedWriter(new FileWriter(percorso))) {
writer.write("Prima riga");
writer.newLine();
writer.write("Seconda riga");
} catch (IOException e) {
System.err.println("Errore durante la scrittura del file: " + e.getMessage());
e.printStackTrace();
}
}
}
Se il file non esiste, viene creato automaticamente. Tuttavia, se esiste già, il contenuto viene sostituito.
5. Appendere testo a un file esistente con File I/O Java
Per aggiungere (appendere) testo in fondo a un file esistente usiamo il costruttore di FileWriter con il flag true. Grazie a questa modalità, il contenuto precedente viene preservato.
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
public class AppendFileTesto {
public static void main(String[] args) {
String percorso = "log.txt";
// true -> modalità append
try (BufferedWriter writer = new BufferedWriter(new FileWriter(percorso, true))) {
writer.write("Nuova riga di log");
writer.newLine();
} catch (IOException e) {
System.err.println("Errore durante l'append al file: " + e.getMessage());
e.printStackTrace();
}
}
}
In questo modo il contenuto esistente nel file viene preservato e il nuovo testo viene aggiunto alla fine. Pertanto, è uno strumento utile per i file di log.
6. File I/O Java: Leggere file binari con FileInputStream
Per i file binari leggiamo byte anziché caratteri. Infatti, è tipico quando si gestiscono immagini, PDF, file compressi, ecc.
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class LetturaFileBinario {
public static void main(String[] args) {
String percorso = "immagine.png";
try (FileInputStream fis = new FileInputStream(percorso)) {
byte[] buffer = new byte[1024];
int bytesLetti;
while ((bytesLetti = fis.read(buffer)) != -1) {
// Elaborazione dei bytes letti
System.out.println("Letti " + bytesLetti + " byte");
}
} catch (FileNotFoundException e) {
System.err.println("File non trovato: " + e.getMessage());
} catch (IOException e) {
System.err.println("Errore durante la lettura del file binario: " + e.getMessage());
e.printStackTrace();
}
}
}
7. File I/O Java: Scrivere file binari con FileOutputStream
Allo stesso modo, per scrivere file binari usiamo FileOutputStream. Nell’esempio seguente, illustriamo come copiare un file binario da una sorgente a una destinazione.
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class ScritturaFileBinario {
public static void main(String[] args) {
String sorgente = "origine.pdf";
String destinazione = "copia.pdf";
try (FileInputStream fis = new FileInputStream(sorgente);
FileOutputStream fos = new FileOutputStream(destinazione)) {
byte[] buffer = new byte[4096];
int bytesLetti;
while ((bytesLetti = fis.read(buffer)) != -1) {
fos.write(buffer, 0, bytesLetti);
}
} catch (IOException e) {
System.err.println("Errore durante la copia del file binario: " + e.getMessage());
e.printStackTrace();
}
}
}
Usiamo un buffer da 4 KB per copiare il file in modo efficiente. Inoltre, questa dimensione è un buon compromesso tra memoria e velocità.
8. Gestione delle eccezioni IOException nel File I/O Java
Quasi tutte le operazioni di gestione dei file in Java possono generare IOException, una eccezione checked che deve essere gestita oppure dichiarata nel metodo con throws.
- Quando accade: file inesistente, permessi mancanti, disco pieno, percorso non valido, errore di rete (per file remoti), ecc.
- Come gestirla: blocchi
try-catchcon messaggi chiari, log dell’errore, eventuale rilancio come eccezione applicativa.
public void leggiFile(String percorso) {
try (BufferedReader reader = new BufferedReader(new FileReader(percorso))) {
// ... lettura ...
} catch (IOException e) {
// Log tecnico
e.printStackTrace();
// Messaggio più leggibile per l'utente
System.err.println("Impossibile leggere il file " + percorso);
}
}
Nelle applicazioni reali è consigliabile usare un sistema di logging (ad esempio, SLF4J + Logback) al posto di printStackTrace(). Infatti, questo approccio è più professionale e facilita il debugging.
9. NIO.2: Path, Paths e Files – File I/O Java moderno
Risorse Utili
- Documentazione ufficiale Java I/O (Oracle)
- Guida a FileReader e BufferedReader (Baeldung)
- Java NIO.2 – Path e Files (Baeldung)
Da Java 7 è disponibile l’API java.nio.file, considerata la modalità moderna per il File I/O in Java. In particolare, rappresenta il successore migliorato delle API tradizionali di java.io. Le classi principali sono:
Path: rappresenta un percorso nel file system (simile aFile, ma più moderno e flessibile).Paths: classe di utilità con metodi statici per crearePath.Files: metodi statici per leggere, scrivere, copiare, cancellare file.
9.1 File I/O Java: Leggere con Files.readAllLines
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
public class LetturaNIO2 {
public static void main(String[] args) {
Path path = Paths.get("input.txt");
try {
List<String> righe = Files.readAllLines(path);
for (String riga : righe) {
System.out.println(riga);
}
} catch (IOException e) {
System.err.println("Errore di I/O con NIO.2: " + e.getMessage());
e.printStackTrace();
}
}
}
9.2 File I/O Java: Scrivere con Files.write
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Arrays;
import java.util.List;
public class ScritturaNIO2 {
public static void main(String[] args) {
Path path = Paths.get("output_nio2.txt");
List<String> righe = Arrays.asList("Riga 1", "Riga 2", "Riga 3");
try {
// Scrittura base (sovrascrive il file se esiste)
Files.write(path, righe);
// Append di un'altra riga
Files.write(path,
Arrays.asList("Riga aggiuntiva"),
StandardOpenOption.APPEND);
} catch (IOException e) {
System.err.println("Errore di I/O con NIO.2 in scrittura: " + e.getMessage());
e.printStackTrace();
}
}
}
Con NIO.2 molte operazioni diventano più concise e leggibili rispetto alle API storiche di java.io. Di conseguenza, è preferibile utilizzare questa libreria nei nuovi progetti.
10. Esempio pratico completo: leggere un file CSV e stampare i dati
Supponiamo di avere un file utenti.csv con il seguente contenuto. In primo luogo, esaminiamo la struttura dei dati:
id;nome;cognome;email
1;Mario;Rossi;mario.rossi@example.com
2;Luca;Bianchi;luca.bianchi@example.com
Vogliamo leggere il file, saltare l’intestazione e stampare i dati in modo formattato. Per questo motivo, usiamo BufferedReader e String.split.
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class LetturaCsv {
public static void main(String[] args) {
String percorso = "utenti.csv";
try (BufferedReader reader = new BufferedReader(new FileReader(percorso))) {
String linea;
boolean primaRiga = true;
while ((linea = reader.readLine()) != null) {
if (primaRiga) {
// Salta l'intestazione
primaRiga = false;
continue;
}
// Supponiamo separatore ';'
String[] campi = linea.split(";");
if (campi.length >= 4) {
String id = campi[0];
String nome = campi[1];
String cognome = campi[2];
String email = campi[3];
System.out.printf("ID: %s, Nome: %s %s, Email: %s%n",
id, nome, cognome, email);
} else {
System.err.println("Riga non valida: " + linea);
}
}
} catch (IOException e) {
System.err.println("Errore durante la lettura del CSV: " + e.getMessage());
e.printStackTrace();
}
}
}
Questo esempio mostra un caso d’uso reale di File I/O in Java per processare dati strutturati provenienti da file CSV. Infine, è uno scenario comune nelle applicazioni aziendali.
11. Articoli correlati
Se stai approfondendo il tema del File I/O in Java, inoltre potrebbero interessarti anche gli articoli correlati seguenti.
- Stringhe in Java: guida pratica a String, StringBuilder e StringBuffer
- Eccezioni in Java: gestione degli errori con try-catch-finally e throws
- Collections in Java: List, Set, Map e collezioni più usate
- Generics in Java: tipi parametrizzati per Collections e metodi
- Lambda e Stream API in Java: programmazione funzionale su collezioni
- OOP in Java: classi, oggetti e incapsulamento
- Array in Java: dichiarazione, inizializzazione e cicli
Rispondi