CodeGym /Corsi /JAVA 25 SELF /Modalità di lavoro con i file: lettura, scrittura, append...

Modalità di lavoro con i file: lettura, scrittura, append

JAVA 25 SELF
Livello 35 , Lezione 3
Disponibile

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
APPEND
Aggiungere in coda; errore se il file non esiste
APPEND + CREATE
Aggiungere in coda; creare il file se non esiste
TRUNCATE_EXISTING
Troncare il file a zero e scrivere nuovi dati
CREATE_NEW
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.

Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION