File IO in Java: Guida Completa

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

file i/o java

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).

  • BufferedReader fornisce il metodo readLine() per leggere una riga alla volta.
  • BufferedWriter offre il metodo newLine() 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-catch con 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

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 a File, ma più moderno e flessibile).
  • Paths: classe di utilità con metodi statici per creare Path.
  • 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.

Commenti

2 risposte a “File IO in Java: Guida Completa”

  1. […] In conclusione, in questo articolo hai visto cosa sono classi e oggetti in Java e come si usano nella pratica tramite costruttori, metodi, modificatori di accesso e la parola chiave this. L’esempio completo con la classe Automobile mostra come questi concetti lavorano insieme in un contesto realistico. Padroneggiare OOP Java classi oggetti è fondamentale per comprendere ereditarietà, polimorfismo e gli altri pilastri dell’OOP in Java. Per continuare il tuo percorso di apprendimento, puoi approfondire i concetti di programmazione concorrente con i thread e scoprire come gestire il file I/O in Java. […]

  2. […] File IO in Java: lettura e scrittura di file per applicazioni pratiche e avanzate. […]

Rispondi

Scopri di più da App Tutorial

Abbonati ora per continuare a leggere e avere accesso all'archivio completo.

Continua a leggere