0 - Stream e File
0 - Stream e File
prende il nome di stream di input, mentre un oggetto nel quale si può scrivere una sequenza di
byte prende il nome di stream di output. Questi oggetti sono specificati nelle classi astratte
InputStream e OutputStream. Dato che gli stream orientati ai byte sono scomodi per elaborare le
informazioni memorizzate in Unicode (tenendo presente che Unicode utilizza due byte per unità di
codice), esiste una gerarchia separata di classi per elaborare tali informazioni che deriva dalle
classi astratte Reader e Writer, le quali prevedono operazioni di lettura e di scrittura che si basano
sulle unità di codice Unicode a due byte invece che su caratteri a un solo byte.
Le famiglie di stream
Java prevede più di 60 diversi tipi di stream per limitare errori di programmazione. Conviene
pertanto suddividere gli stream in base all’utilizzo che si intende farne. Alla base ci sono quattro
classi astratte: InputStream, OutputStream, Reader e Writer. Non si costruiscono oggetti di questi
tipi, che possono invece essere restituiti da altri metodi; per utilizzare metodi che leggono e
scrivono stringhe e numeri, è necessario ricorrere ad apposite classi figlie. D’altra parte, ci sono
stream che svolgono compiti molti utili; per esempio ZipInputStream e ZipOutputStream
permettono di leggere e scrivere file nel tipico formato di compressione ZIP.
Come scrivere output di testo
Per l’output di testo si può utilizzare un PrintWriter, in grado di visualizzare stringhe e numeri in
formato testuale.
Per scrivere in un PrintWriter si utilizzano gli stessi metodi print e println che si utilizzano
con System.out. Si possono utilizzare questi metodi per visualizzare numeri, caratteri,
valori boolean, stringhe e oggetti.
Se il writer è impostato in modalità autoflush, tutti i caratteri del buffer vengono inviati alla
loro destinazione ogni volta che si chiama il println.
L’impostazione predefinita prevede che l’autoflush non sia attivo. E’ possibile attivarlo (o
disattivarlo) , utilizzando il costruttore PrintWriter(Writer, boolean) e passando il valore
boolean appropriato come secondo argomento:
Il metodo readLine restituisce null quando non è più disponibile altro input
String line;
while ( (line = in.readLine()) != null ) {
// istruzioni da eseguire con line
}
Scrivere output delimitato
Si vedrà adesso come memorizzare un array di record Impiegato nel formato delimitato. Ciò
significa che ogni record è memorizzato in una riga separata. I campi istanza sono separato l’uno
dall’altro mediante dei delimitatori. Come delimitatore si utilizza una barra verticale (|). Anche i
due punti (:) sono una scelta comune. Non è ovviamente da trascurare ciò che potrebbe accadere
se ci fosse un | in una delle stringhe salvate.
Scrivere i record è abbastanza semplice. Dato che si scrive un file di testo, si utilizza una classe
PrintWriter. Si scrivono semplicemente tutti i campi, seguiti da | oppure, se si tratta dell’ultimo
campo, da \n. Infine per tenere fede all’idea che la classe è responsabile della scrittura del proprio
stato si aggiunge un metodo writeData alla classe Impiegato. Per leggere i record si legge una riga
per volta e si separano i campi. Per fare ciò si utilizza la classe StringTokenizer.
Ciò significa che uno quasi dei caratteri nella stringa può svolgere la funzione di
delimitatore.
Dopo aver costruito un oggetto StringTokenizer, si possono utilizzare i suoi metodi per
estrarre velocemente i token dalla stringa. Il metodo nextToken restituisce il token
successivo all’ultimo letto, mentre il metodo hasMoreTokens restituisce true se sono
disponibili altri token. Il ciclo seguente elabora tutti i token:
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken();
// elaborazione token
}
Leggere input delimitato
Per leggere un record di oggetti si deve semplicemente leggere una riga utilizzando il metodo
redLine della classe BufferedReader.
Così come con il metodo writeData, si aggiunge un metodo readData della classe
Impiegato. Per esempio:
La classe StringBuilder
Quando si elabora input, spesso è necessario costruire stringhe a partire dai singoli caratteri o da
unità di codice Unicode, e risulterebbe piuttosto inefficiente utilizzare il concatenamento delle
stringhe. Ogni volta che si aggiunge un carattere a una stringa, l’oggetto stringa deve trovare
nuovo spazio in memoria per contenere la stringa più grande, e questo si traduce in uno spreco di
tempo. Aggiungere ulteriori caratteri significa che la stringa deve essere riallocata più e più volte.
L’utilizzo di StringBuilder evita questo genere di problema. Infatti funziona in modo simile ad
ArrayList, gestisce un array char[] che può espandersi o ridursi a richiesta; infine si utilizza il
metodo toString per convertire il contenuto in un oggetto String effettivo.
A questo punto per memorizzare un oggetto si utilizza il metodo writeObject della classe
ObjectOutputStream, come indicato nella porzione di codice che segue:
Per recuperare gli oggetti nello stesso ordine con il quale sono stati scritti si utilizza il
metodo readObject:
Ogni chiamata readObject legge un altro oggetto di tipo Object ed è pertanto necessario
definire un cast corrispondente al tipo corretto. Se non è necessario impostare il tipo
corretto oppure non è possibile ricordarlo, si può definire un cast a una qualsiasi
superclasse oppure lasciare il tipo Object. Se si deve interrogare dinamicamente il tipo
dell’oggetto, si può utilizzare il metodo getClass .
E’ necessario effettuare una modifica a ogni classe che si voglia rendere leggibile e scrivibile
tramite uno stream di oggetti. La classe deve implementare l’interfaccia Serializable:
L’interfaccia Serializable non ha metodi, per cui non è necessario modificare le classi in
alcun modo. Da questo punto di vista la situazione è simile all’interfaccia Cloneable.
Tuttavia, per effettuare l’operazione di clonazione su una classe, si deve eseguire l’override
del metodo clone della classe Object. Per impostare la serializzazione della classe non si
deve fare nient’altro.
E’ inoltre possibile passare come parametro del metodo writeObject un array di oggetti
Impiegato che contiene, ad esempio, due oggetti Impiegato e uno Manager:
Analogamente anche la lettura viene effettuata con una sola operazione applicando un
cast al valore restituito:
Si deve tenere conto che questo metodo, nonostante sia abbastanza interessante, risulta di solito
più lento di un metodo che costruisce esplicitamente un nuovo oggetto e copia o clona i campi di
dati.
Il costruttore più semplice per un oggetto File richiede un nome di file (completo). Se non
include il percorso del nome, Java utilizza la directory corrente, come nell’esempio:
Questa istruzione fornisce un oggetto file con il nome assegnato posto nella directory
corrente. Una chiamata di questo costruttore non crea un file con il nome assegnato, se il
file non esiste, come avveniva invece con uno dei costruttori della classe di stream
E’ possibile creare un file utilizzando il metodo createNewFile della classe File. Il metodo
createNewFile crea il file solo se non ne esiste già uno con lo stesso nome e restituisce un
valore boolean per segnalare se l’operazione ha avuto successo.
D’altra parte, dopo aver ottenuto un oggetto File il metodo exists della classe File segnala
se esiste un file con il nome assegnato.
Un altro costruttore dell’oggetto File che prende come parametri l’indirizzo della directory
e il nome del file: File(String path, String name)
In questa istruzione l’oggetto File rappresenta una directory e, come in precedenza, se dir
è null, il costruttore crea un oggetto File nella directory corrente.
Può confondere le idee il fatto che un oggetto File può rappresentare sia un file sia una
directory; Si utilizzano i metodi isDirectory e isFile per conoscere la natura dell’oggetto.
Per costruire un oggetto che rappresenta un direcotry, nel costruttore File si deve
semplicemente indicare il nome della directory:
Se questa directory non esiste ancora, è possibile crearla con il metodo mkdir:
tempDir.mkdir();
Se un oggetto File rappresenta una directory, si utilizza list() per ottenere l’array dei nomi
dei file presenti nella directory.