Nota: le classi per input / output si trovano nel package
java.io, percio' in testa ai file sorgenti bisogna importare
tale package:
import java.io.*;
Le due classi Java InputStream e OutputStream rappresentano un generico
canale di input e di output.
Le due sotto-classi FileInputStream e FileOutputStream
rappresentano un canale di input e di output che consiste in un file.
I costruttori di queste due classi prendono come argomento il nome
del file da aprire in lettura e in scrittura (a seconda del caso):
Pero' le funzioni di scrittura fornite da OutputStream sono troppo
di basso livello, scrivono un byte alla volta.
E' piu' comodo usare, per scrivere su file, la classe PrintStream.
La classe Java PrintStream rappresenta un canale di output che puo' scrivere stringhe, con le funzioni:
Come mi procuro un oggetto di classe PrintStream?
Anche le funzioni di lettura fornite da InputStream sono
di basso livello, leggono un byte (= un carattere) alla volta
ritornando il suo codice ASCII come numero intero.
Purtroppo, per leggere su file, non esiste in Java
un equivalente della classe PrintStream usata per scrivere.
Allora definiamo noi una nostra classe che permetta di leggere agevolmente i vari tipi base: stringhe, interi, double...
Abbiamo due classi Java per le stringhe:
Per leggere una stringa usiamo un oggetto di classe StringBuffer,
inizialmente vuoto,
e a questo concateniamo in fondo, uno alla volta, i caratteri letti
da file.
Ogni carattere viene restituito dalla funzione "read" della
classe InputStream come un numero intero (il suo codice ASCII),
che convertiamo forzatamente a carattere:
int cod = fd.read();
char c = (char)cod;
dove fd e' un oggetto di tipo InputStream.
Ci fermiamo quando incontiamo uno spazio bianco, oppure la
fine del file.
La funzione Java Character.isWhitespace(char c) controlla
se un carattere c e' uno spazio bianco.
Il carattere marchio di fine file ha codice ASCII = -1.
Inoltre bisogna saltare gli eventuali spazi bianchi prima dell'inizio
della stringa.
Questo legge una stringa e la ritorna:
StringBuffer s = new StringBuffer(""); // stringa modificabile vuota int cod = skipWhitespace(); // salta spazi (ved. dopo) while ( (cod!=-1) && !Character.isWhitespace( (char)cod )) { s.append( (char)cod ); // concatena carattere letto cod = fd.read(); // legge prossimo carattere } return new String(s); // stringa finale non modificabiledove skipWhitespace e' la nostra funzione che salta gli spazi bianchi e ritorna il codice del primo carattere non bianco, implementata cosi':
int cod = (int)(' '); // inizializzo con spazio bianco while (Character.isWhitespace( (char)cod )) // finche' bianco { cod = fd.read(); // va avanti a leggere carattere if (cod == -1) return -1; // trovata fine file } return cod; // codice del primo non bianco
Definiamo la nostra classe MyReader che contiene al suo interno un oggetto di classe InputStream da cui leggere, e ha le seguenti funzioni pubbliche:
Vedere sorgente Java della classe MyReader.
Usare la classe MyReader per leggere da input le coordinate di due punti, calcolare e stampare la distanza tra i due punti.
Usate la classe PrintStream per scrivere su file i numeri primi trovati dal crivello di Eratostene.
Quando devo leggere / scrivere non valori di base ma oggetti di una classe, ho due alternative:
Decidere una sintassi con cui scrivere gli oggetti
della mia classe in termini di valori di base.
In lettura fare il "parsing" (controllo sintattico) del contenuto
del file secondo la stessa sintassi.
Rendere la classe persistente e lasciare che Java scriva l'oggetto, e poi lo rilegga, secondo la sua propria sintassi.
La prima alternativa mi fa scrivere piu' codice, ma produce un file intelleggibile che posso editare a mano se voglio.
La seconda alternativa e' quasi gratis, ma produce un file non unanamente leggibile. Inoltre se cambio qualcosa nella classe cambia anche la sintassi che Java usa per gli oggetti, e Java non e' piu' in grado di rileggere oggetti scritti precedentemente.
Esempio: rettangolo che sa scriversi su standard output e leggersi da standard input.
Sintassi che ho deciso per i rettangoli:
class Rectangle { int length, width; int area() { return length * width; } void print() { System.out.println("Rectangle " + length + " x " + width); } void read() { try // le funzioni del reader possono sollevare eccezione { MyReader rd = new MyReader(System.in); String s = rd.readString(); if (!rd.isKeyword(s,"Rectangle")) return; // non fa nulla length = rd.readInteger(); s = rd.readString(); if (!rd.isKeyword(s,"x")) return; // non fa nulla width = rd.readInteger(); } catch(Exception exc) {} // se errore non fa nulla } public static void main(String[] args) { Rectangle r = new Rectangle(); r.read(); r.print(); } }
Il main chiede un rettangolo da standard input (secondo la sintassi decisa) e poi lo stampa su standard output. Esempio di uso, digitare:
java Rectangle Rectangle 33 x 24
Oggetti persistenti = oggetti che sono capaci a scriversi su file e rileggersi da file. Servono per "ricordare" lo stato di un oggetto tra una esecuzione e l'altra del programma.
Classi persistenti implementano l'interfaccia Serializable, che non ha metodi, ma occorre dichiarare che una classe implementa Serializable per poterla scrivere e rileggere attraverso i metodi di ObjectOutputStream e ObjectInputStream.
Classe le cui istanze rappresentano file e sono in grado di scrivere oggetti appartenenti a classi che implementano l'interfaccia Serializable.
Costruzione di un'istanza di ObjectOutputStream, per es. sia "pippo.txt" il nome del file, e scrittura di un oggetto sul file:
FileOutputStream fos = new FileOutputStream("pippo.txt"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(...oggetto da scrivere...); oos.close(); fos.close();Nota: tutte queste operazioni possono sollevare eccezioni di classe IOException.
Classe le cui istanze rappresentano file e sono in grado di leggere oggetti appartenenti a classi che implementano l'interfaccia Serializable.
Costruzione di un'istanza di ObjectInputStream, per es. sia "pippo.txt" il nome del file, e lettura di un oggetto dal file:
FileInputStream fis = new FileInputStream("pippo.txt"); ObjectInputStream ois = new ObjectInputStream(fis); ...oggetto da leggere... = (...classe dell'oggetto...)ois.readObject(); ois.close(); fis.close();Note:
Esempio: rettangolo persistente
class Rectangle implements Serializable { int length, width; int area() { return length * width; } // Nota: non devo scrivere funzioni per lettura / scrittura public static void main(String[] args) { Rectangle r = new Rectangle(); r.length = 30; r.width = 77; try { FileOutputStream fos = new FileOutputStream("pippo.txt"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(r); oos.close(); fos.close(); FileInputStream fis = new FileInputStream("pippo.txt"); ObjectInputStream ois = new ObjectInputStream(fis); r = (PRectangle)ois.readObject(); ois.close(); fis.close(); System.out.println("r.length riletta = " + r.length); System.out.println("r.width riletta = " + r.width); } catch(Exception exc) {} } }