• Non ci sono risultati.

Un file strutturato è uno dei due tipi di file che java tratta. La struttura di un file strutturato è definita nel programma. In

particolare, affinché il contenuto di un file strutturato possa essere interpretato è necessario conoscere la sua struttura. Ad esempio, un file composto da sequenze di due numeri interi, un numero reale e una stringa, può essere letto dal programma solo se in quest’ultimo è specificato questo tipo di ordine. I file strutturati utilizzano le classi basate sui byte per le operazioni di I/O.

Il modo con cui java lavora con i file strutturati è detta serializzazione.

L’apertura di un file strutturato per le operazioni di output, cioè per le operazioni di scrittura, è rappresentata dalla creazione e dalla concatenazione dei seguenti due stream:

FileOutputStream f = new FileOutputStream("elenco.dat");

ObjectOutputStream fOUT = new ObjectOutputStream(f);

Nota: la prima istruzione crea un oggetto ,“f”, di classe FileOutputStream, che permette di aprire un canale di

comunicazione tra programma e il file strutturato “elenco.dat”.

Invece la seconda istruzione crea un oggetto, fOUT, di classe ObjectOutputStream, che è in grado di fare delle operazioni di scrittura su un file strutturato. In particolare, attraverso la

concatenazione dei due stream “f” e “fOUT” , cioè nel costruttore della classe ObjectOutputStream specifico come parametro

l’oggetto “f” , affinché posso effettuare delle operazioni di scrittura sul file strutturato “elenco.dat”.

Rappresentando graficamente gli stream si ottiene la seguente figura:

Se il file “elenco.dat” esiste l’istruzione FileOutputStream f = new FileOutputStream("elenco.dat"); permette l’apertura di un canale di comunicazione tra programma e file per le operazioni di

riscrittura del file (la riscrittura di in file sostituisce il contenuto del file con un nuovo contenuto). Per evitare questo, cioè per accodare dei valori al contenuto del file, che già esiste, bisogna fare questo:

FileOutputStream f = new FileOutputStream("elenco.dat", true);

Nota: il valore del secondo parametro del costruttore della classe FileOutputStream, indica la condizione di accodamento (append), che se è true il file viene aperto per mettere in coda ai dati già preesistente del file dei nuovi dati. Invece se è false, o non è presente la condizione di accodamento, il file viene aperto in scrittura e questo comporta la cancellazione di eventuali dati già preesistenti.

La chiusura sia di uno stream di input, sia di uno stream di output viene fatta invocando il metodo close() in questo modo:

f.close( );

Le operazioni che gestiscono l’ I/O possono generare delle

eccezioni. Per esempio quando viene aperto uno stream di input per la lettura di un file strutturato, può generare l’eccezione che il file non è stato trovato, poiché ho specificato il nome di un file non esistente. Per questo devo racchiudere tutte le istruzioni che riguardano la gestione dei file in un blocco try...catch, per

catturare le eccezioni.

Dopo che viene aperto lo stream di output, è possibile utilizzare diversi metodi per la scrittura su file. Di preciso per ogni tipo di dato esiste un particolare metodo per la scrittura su file.

Per esempio:

fOUT.writeInt( 25400);

fOUT.writeDouble(12.36);

Prima di chiudere il canale di comunicazione tra programma e file è necessario eseguire il metodo flush() che serve per scrivere su disco tutti i dati che sono attualmente contenuti nel buffer dello stream. Il metodo più importante è writeObject(), che consente di salvare un oggetto su un file. In particolare è java ad preoccuparsi di salvare la struttura dell’oggetto memorizzando gli attributi non statici dell’oggetto. L’oggetto salvato sul file(cioè sul disco)

diventa persistente, cioè sopravvive alla singola esecuzione del programma (in java, quando il riferimento all’oggetto viene perso l’oggetto viene distrutto automaticamente). Ma prima di salvare un oggetto in un file, per renderlo effettivamente persistente dobbiamo implementare nella classe che ha istanziato l’oggetto l’interfaccia Serializable.

Esempio: memorizzare nel file elenco.dat le informazioni di 3 persone:

Nota1: l’esecuzione del metodo flush() subito dopo le tre operazioni di scrittura rende effettivo il trasferimento dei dati verso la destinazione.

Nota2: siccome ho usato con tutte le istruzioni che mi

implementano le operazioni di output un singolo blocco try...catch (e non per ogni istruzione un blocco try...catch), non saprei quale

tipo di eccezione potrebbe venire catturata. Quindi decido di usare come parametro del blocco catch una variabile di tipo Exception, che cattura ogni tipo di errore.

Nota3: vedere nel dettaglio il metodo getMessage().

L’apertura di un file strutturato per effettuare operazioni di input, cioè operazioni di lettura, avviene mediante la creazione e la concatenazione di due stream:

FilelnputStream f = new FileInputStream("elenco.dat");

ObjectlnputStream fIN = new ObjectInputStream(f);

Nota: la prima istruzione crea un oggetto, “f”, di classe FileInputStream, che permette di aprire un canale di

comunicazione tra il file strutturato “elenco.dat” e il programma, per permettere di effettuare delle operazioni di lettura sul file.

Invece la seconda istruzione crea un oggetto, fIN, di classe ObjectInputStream, che è in grado di fare delle operazioni di lettura su un file strutturato. In particolare, concatenando i due stream “f” e “fIN”, cioè mettendo come parametro del costruttore della classe ObjectInputStream l’oggetto “f”, posso effettuare delle operazioni di lettura sul file strutturato “elenco.dat”.

Rappresentando graficamente gli stream si ottiene la seguente figura:

Per

ogni tipo di dato contenuto in un file strutturato esiste un metodo di classe ObjectInputStream, che viene utilizzato per leggere il dato stesso. In linea generale, possiamo dire che il metodo che legge un tipo di dato contenuto in un file strutturato assume questa forma readDato, dove Dato viene sostituito con il tipo di dato che

vogliamo leggere. Per evitare inconsistenze è meglio leggere i dati nello stesso ordine in cui sono stati salvati.

La lettura di un oggetto memorizzato in un file strutturato implica che l’oggetto può essere nuovamente riutilizzato. Il metodo

readObject legge un oggetto memorizzato in un file strutturato. In particolare, il metodo readObject restituisce un oggetto di tipo

Object, che per farlo ritornare alla classe originaria viene utilizzato il casting.

Se non conosciamo a priori quanti dati ci sono in un file

strutturato, allora possiamo usare un ciclo infinito per leggere ogni singolo dato del file strutturato, finché non viene generata

l’eccezione EOFException. Questa eccezione segnale che siamo giunti alla fine del file, e non ci sono più dati da leggere, allora possiamo interrompere il ciclo.

Esempio che legge dal file elenco.dat e stampa le informazioni delle persone memorizzate:

Nota1: impostando la condizione del ciclo while a true viene attivato un ciclo infinito che termina solo quando il blocco catch cattura l’eccezione del tipo EOFExeption, e quindi l’istruzione break può essere eseguita.

Nota2: l’istruzione p=(Persona) fIN.readObject(); crea un oggetto di tipo Object e memorizza i valori letti nel file strutturato, negli attributi del nuovo oggetto creato. L’operatore di casting () mi permette di tornare alla classe originaria (cioè la classe a cui apparteneva l’oggetto prima di essere memorizzato nel file strutturato), cioè Persona.

Documenti correlati