Stream e file in Java: i file di testo

In quest’articolo vediamo com’è possibile operare in Java con gli stream sui file di testo. Per farlo conviene riprendere una figura già utilizzata nell’articolo introduttivo agli stream in Java, a cui si rimanda (link), e del quale riprenderemo anche alcuni concetti.

stream02

La figura mostra il diagramma UML di alcune delle classi del package java.io e mette in risalto che Java usa due categorie di oggetti fondamentali per gestire gli stream di input/output, ovvero due distinte gerarchie di classi:

  1. Le classi che derivano dalle classi astratte Reader e Writer (alcune di queste evidenziate in giallo), che sono orientate alla gestione di flussi di caratteri e che chiameremo più comodamente lettori’ e ‘scrittori’, utili per lavorare con il formato testo. Insieme ad esse in figura è stata riportata ed evidenziata anche la classe Scanner che, invece, appartiene al package java.util e che viene introdotta per la sua versatilità. Di questa classe se ne parla in un altro articolo a cui si rimanda: link.
  2. Le classi che derivano dalle classi astratte InputStream e OutputStream (alcune di queste evidenziate in arancione), utili per la realizzazione di stream orientati al byte e che gestiscono sequenze di byte che chiameremo più comodamente flussi di input/output, utili per lavorare nel formato binario. Di queste classi se ne parla in un altro articolo a cui si rimanda: link.

I file di testo

Operare con un file di testo in Java è immediato in quanto, grazie alla prima gerarchia di classi suddetta, è molto semplice trasformare un flusso di input/output su file in un lettore/scrittore e viceversa.

  • Leggere un file di testo

Il modo più semplice per leggere in Java un file di testo é il seguente:

La classe FileReader permette di ottenere un lettore a partire da un un flusso di input proveniente da un file (nell’esempio di sopra, il file nomeFile.txt che deve essere passato al costruttore come stringa). Operare direttamente con un lettore di questa classe, però, è alquanto scomodo in quanto essa mette a disposizione solo il metodo read() che restituisce un intero che può valere:

  • -1 quando si arriva alla fine del file;
  • un intero tra 0 e 16383, che rappresenta il codice di un carattere UNICODE.

Per cui per ciascun carattere del file di testo, bisognerebbe controllare se il numero letto è diverso da -1 e, in questo caso, trasformare l’intero in un char usando l’operatore di cast. Procedura alquanto scomoda, se si considera che noi tipicamente abbiamo bisogno di leggere righe di testo. Questo problema viene risolto incapsulando il lettore di classe FileReader in un oggetto di classe BufferedReader che ci permette di utilizzare i metodi read() e readLine() che, rispettivamente, restituisco un carattere e una stringa, provenienti dallo stream di caratteri fornito come parametro al costruttore.

Si fa notare che l’apertura del file avviene con la creazione dell’oggetto di classe FileReader che, se il file non esiste, lancia un’eccezione FileNotFoundException a gestione obbligatoria. La classe BufferedReader, invece, gestisce la possibilità che si verifichino dei malfunzionamenti che possano interrompere lo stream, lanciando un’eccezione di classe IOException. Queste eccezioni vanno gestite inserendo le istruzioni di sopra all’interno di un blocco try-catch.

Per la lettura di dati numerici vale quanto già detto nell’articolo in cui si è parlato di input da tastiera a cui si rimanda (link).

Una soluzione equivalente alla precedente per leggere un file di testo in Java, è la seguente:

Quando il file finisce, il metodo hasNextLine() di Scanner restituisce un valore false. Questa soluzione ha il vantaggio di permettere di sfruttare i molteplici metodi della classe Scanner (next, nextLine, ecc.) che viene illustrata in un articolo dedicato a cui si rimanda: link.

  • Scrivere in un file di testo

Il modo più semplice per scrivere in Java in un file di testo è:

Con la creazione dell’oggetto di classe FileWriter, il file da aprire in scrittura (nell’esempio nomeFile.txt, che deve essere passato al costruttore come stringa) viene creato se non esiste già, altrimenti viene semplicemente aperto. Si noti che il secondo parametro passato al costruttore serve, se impostato a true, per aprire il file in append, ossia per scrivere il nuovo contenuto in aggiunta a quello già presente. Esso è opzionale e per default il file viene aperto in sovrascrittura (riscrittura). L’oggetto di classe FileWriter viene incapsulato in uno di classe PrintWriter per sfruttare i comodi metodi print() e println() di questa classe che permettono di scrivere qualunque dato Java con una conversione automatica in stringa.