La gestione dei file sequenziali in Java

Argomento molto diffuso negli altri linguaggi di programmazione. Vediamo brevemente una modalità, non l’unica, di uso dei file in Java per arricchire i nostri esercizi di una memoria tre esecuzioni diverse. L’esempio è forse un po’ più ostico rispetto a quelli che si trovano similarmente online, ma ho voluto proporre direttamente un caso di studio più realistico e completo.

Simuliamo di avere una classe Alunno, con attributi standard, stringhe di testo, interi, numeri decimali e anche una data. Di questa classe vogliamo creare alcune istanze di esempio, scriverle su file, e rileggere stampando il contenuto del medesimo file.

La classe Alunno

Vogliamo generare uno o più record da scrivere nel file e lo facciamo verosimilmente con una classe dagli attributi variegati. Il lettore anche con poca esperienza di OOP non dovrebbe aver problemi ad afferrare il codice di questa classe. L’unica particolarità è il metodo toRecord che fa il paio a toString. Si occupa brevemente di trasporre tutti i campi in una stringa compatta con i singoli campi suddivisi da ; Notiamo anche la trasposizione della data in stringa: senza questo sistema di formattazione, la data verrebbe stampata in modo esteso con il nome del mese e l’ora.

package iisteramo.file;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 *
 * @author alfredo centinaro
 */
public class Alunno 
{
    String  nome;
    String  cognome;
    Date    nascita;
    double  peso;
    char    sesso;
    int     altezza;

    public Alunno() {
    }

    public Alunno(String nome, String cognome, Date nascita, double peso, char sesso, int altezza) {
        this.nome = nome;
        this.cognome = cognome;
        this.nascita = nascita;
        this.peso = peso;
        this.sesso = sesso;
        this.altezza = altezza;
    }
    
    public String getNome() {
        return nome;
    }

    public void setNome(String nome) {
        this.nome = nome;
    }

    public String getCognome() {
        return cognome;
    }

    public void setCognome(String cognome) {
        this.cognome = cognome;
    }

    public Date getNascita() {
        return nascita;
    }

    public void setNascita(Date nascita) {
        this.nascita = nascita;
    }

    public double getPeso() {
        return peso;
    }

    public void setPeso(double peso) {
        this.peso = peso;
    }

    public char getSesso() {
        return sesso;
    }

    public void setSesso(char sesso) {
        this.sesso = sesso;
    }

    public int getAltezza() {
        return altezza;
    }

    public void setAltezza(int altezza) {
        this.altezza = altezza;
    }
     
    public String toRecord()
    {
        return this.nome        + ";" + 
                this.cognome    + ";" +
                new SimpleDateFormat("dd/MM/yyyy").format(this.nascita)  + ";" + 
                this.peso       + ";" +
                this.sesso      + ";" +
                this.altezza    +
                ";";
    }        
}

Scrittura

La scrittura è semplice ed indolore con le due classi FileWriter e BufferedWriter. La prima si occupa di gestire l’apertura del file o crearlo se non ci fosse. Occhio al parametro true che serve per aprire il file in sovrascrittura se false e in appeso se true, aggiungendo quindi righe a quelle che già si trovano nel file. Il BufferedWriter si occupa di prendere una stringa di testo e depositarla nel file aperto. Qui per noi vengono passati i dati di una istanza della classe Alunno separati dal ;. Il file quindi viene costruito con dimensioni dinamiche delle varie righe che non hanno lunghezza fissa complessiva, ne lunghezza fissa dei singoli campi. Non è l’unica scelta possibile, ma è sicuramente la più gettonata che assomiglia ai blasonati file .CSV di Excel per esportare/importare dati in formato trasversale a svariate applicazioni. LE funzioni newLine, flush e close sono intuitive: vanno a capo, forzano la scrittura di un record istantaneamente obbligando il sistema operativo a non ottimizzare la scrittura su disco secondo i suoi algoritmi e chiudono il file. La scrittura genera o un throw da dichiarare nel nome della funzione o esige un try catch per intercettare errori di apertura/scrittura del file.

    public void scrivi(String _record)
    {

        try 
        {
            FileWriter file = new FileWriter(this.nomeFile, true);  //true = append
            BufferedWriter output = new BufferedWriter(file);
            System.out.println("Sto scrivendo... ");
            System.out.println(_record);
            output.write(_record);
            output.newLine();
            //output.flush();
            output.close();
        }
        catch(IOException e) 
        {
            e.printStackTrace();
        }
    
    }

Lettura

La fase di lettura prevede una classe che si occupa di bufferizzare il contenuto del file, riga per riga. La prima riga la leggiamo secca con readline(), le successive usiamo un ciclo per automatizzare il procedimento. Della riga letta, eseguiamo una funzione di splitting che legge il carattere divisorio ; e spezza in un opportuno vettore i vari campi. L’esempio è volutamente completo di tutti i tipi di dato più usati. Con i singoli elementi letti, qui inizializziamo un oggetto Alunno e ne stampiamo il contenuto. Ancora più verosimilmente, la lettura di un file potrebbe riempire un intero vettore di oggetti. Non scordate di leggere una nuova linea alla fine ma dentro al while! La gestione degli errori in fase di lettura è piuttosto ostica. Qui lanciamo throws in fase di dichiarazione per la mancanza del file, impossibilità a leggere o, nel caso di date, errore di conversione del formato letto su file a quello da rendere poi nell’opportuno campo.

public void leggi() throws FileNotFoundException, IOException, ParseException
    {
        BufferedReader in
            = new BufferedReader(new FileReader(this.nomeFile));
        String linea = in.readLine();
        String lineaSplit[];
        
        while (linea != null)
        {
            lineaSplit = linea.split(";");
            String nome = lineaSplit[0];
            String cognome = lineaSplit[1];
            Date   dn = new SimpleDateFormat("dd/MM/yyyy").parse(lineaSplit[2]);
            Double peso = Double.valueOf(lineaSplit[3]);
            char sesso = lineaSplit[4].charAt(0);
            int altezza = Integer.valueOf(lineaSplit[5]);
            
            Alunno al = new Alunno(nome,cognome, dn, peso, sesso, altezza);
            
            System.out.println("-----------------------------");
            System.out.println(al.toRecord());
            
            linea = in.readLine();
        }

La classe di Test

La classe test contiene il main per eseguire la nostra applicazione, un attributo per la gestione del path/nome del file da scrivere, i metodi leggi e scrivi. L’idea è di creare alcune istanze di alunno, scriverle su file e quindi rileggerle da file stampando il risultato incrociato delle due operazioni.

package iisteramo.file;

/**
 *
 * @author alfredo centinaro
 */
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class Test 
{
    String nomeFile;

    public String getNomeFile() {
        return nomeFile;
    }

    public void setNomeFile(String nomeFile) {
        this.nomeFile = nomeFile;
    }
    
    public void leggi() throws FileNotFoundException, IOException, ParseException
    {
        BufferedReader in
            = new BufferedReader(new FileReader(this.nomeFile));
        String linea = in.readLine();
        String lineaSplit[];
        
        while (linea != null)
        {
            lineaSplit = linea.split(";");
            String nome = lineaSplit[0];
            String cognome = lineaSplit[1];
            Date   dn = new SimpleDateFormat("dd/MM/yyyy").parse(lineaSplit[2]);
            Double peso = Double.valueOf(lineaSplit[3]);
            char sesso = lineaSplit[4].charAt(0);
            int altezza = Integer.valueOf(lineaSplit[5]);
            
            Alunno al = new Alunno(nome,cognome, dn, peso, sesso, altezza);
            
            System.out.println("-----------------------------");
            System.out.println(al.toRecord());
            
            linea = in.readLine();
        }
        
    }        
    
    public void scrivi(String _record)
    {

        try 
        {
            FileWriter file = new FileWriter(this.nomeFile, true);  //true = append
            BufferedWriter output = new BufferedWriter(file);
            System.out.println("Sto scrivendo... ");
            System.out.println(_record);
            output.write(_record);
            output.newLine();
            //output.flush();
            output.close();
        }
        catch(IOException e) 
        {
            e.printStackTrace();
        }
    
    }
    
    public static void main (String[] args) throws ParseException 
    {
        Alunno a1 = new Alunno("Alfredo", "Centinaro", new Date("02/01/1982"), 75.0, 'M', 173);
        Alunno a2 = new Alunno("Mario", "Rossi", new Date("07/07/1977"), 81.0, 'M', 170);
        Alunno a3 = new Alunno("Serena", "Bianchi", new Date("07/07/1990"), 50.0, 'F', 170);  
        
        Test t = new Test();
        t.setNomeFile("prova.txt");
        t.scrivi(a1.toRecord());
        t.scrivi(a2.toRecord());
        t.scrivi(a3.toRecord());
        
         
        try
        {
          System.out.println("Sto leggendo... ");
          t.leggi( );
        }
        catch(FileNotFoundException e){System.out.println("File non esistente");}
        catch(IOException e){System.out.println("Qualcosa è andato storto nella lettura");}  
    }
    
}

I listati completi su GitHub -> https://github.com/alfredocentinaro/esercizi-java/tree/main/gestione-file

Eseguilo su Replit ->

Ultima modifica 20 Dicembre 2022