Il 0% ha trovato utile questo documento (0 voti)
15 visualizzazioni8 pagine

0 - Stream e File

In Java, gli stream di input e output permettono di leggere e scrivere sequenze di byte o unità di codice. Esistono classi astratte per gestire stream di byte e stream di caratteri Unicode. Gli stream possono essere usati per leggere e scrivere file in formato testuale o serializzato.

Caricato da

Giuseppe Perrino
Copyright
© © All Rights Reserved
Per noi i diritti sui contenuti sono una cosa seria. Se sospetti che questo contenuto sia tuo, rivendicalo qui.
Formati disponibili
Scarica in formato PDF, TXT o leggi online su Scribd
Il 0% ha trovato utile questo documento (0 voti)
15 visualizzazioni8 pagine

0 - Stream e File

In Java, gli stream di input e output permettono di leggere e scrivere sequenze di byte o unità di codice. Esistono classi astratte per gestire stream di byte e stream di caratteri Unicode. Gli stream possono essere usati per leggere e scrivere file in formato testuale o serializzato.

Caricato da

Giuseppe Perrino
Copyright
© © All Rights Reserved
Per noi i diritti sui contenuti sono una cosa seria. Se sospetti che questo contenuto sia tuo, rivendicalo qui.
Formati disponibili
Scarica in formato PDF, TXT o leggi online su Scribd
Sei sulla pagina 1/ 8

Nel linguaggio di programmazione Java, un oggetto dal quale si può leggere una sequenza di byte

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.

 PrintWriter deve essere associato a un writer di destinazione:

PrintWriter out = new PrintWriter(new FileWriter(“impiegato.txt”);

 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:

PrintWriter out = new PrintWriter(new FileWriter(“impiegato.txt”), true);

Come leggere input di testo


Prima di JDK 5.0, l’unico modo per elaborare l’input di testo era offerto dalla classe
BufferedReader, il cui metodo readLine permette di leggere una riga di testo.

 E’ necessario combinare un BufferedReader con una sorgente di input:

BufferedReader in = new BufferedReader(new FileReader(“impiegato.txt”);

Il metodo readLine restituisce null quando non è più disponibile altro input

 Di seguito è indicato un tipico ciclo di 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.

StringTokenizer e testo delimitato


Quando si legge una riga di input, si acquisisce una singola stringa lunga che si vuole suddividere in
stringhe individuali. Ciò significa trovare i delimitatori | ed estrarre le sequenze di caratteri che si
trovano tra essi. Queste parti prendono di solito il nome di token. La classe StringTokenizer,
implementata in java.util, è stata progettata per rispondere esattamente a questo proposito, la
quale offre un modo semplice per suddividere una stringa lunga che contiene testo delimitato.

 Quando si costruisce l’oggetto tokenizer, si specificano i caratteri da riconoscere come


delimitatori:

StringTokenizer tokenizer = new StringTokenizer(line, “|”);

 E’ possibile anche indicare delimitatori multipli nella stringa, per esempio:

StringTokenizer tokenizer = new StringTokenizer(line, “|,;”);

Ciò significa che uno quasi dei caratteri nella stringa può svolgere la funzione di
delimitatore.

 Se non si specifica un set di delimitatori, l’impostazione predefinita prevede tutti i caratteri


corrispondenti a spazi bianchi: spazio, tabulazione, nuova riga e invio a capo.

 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.

 Di seguito è indicato il codice necessario per leggere un record in una stringa:

Bufferedreader in = new BufferedReader(new FileReader(“impiegato.txt”));


...
String line = in.readLine();

 Così come con il metodo writeData, si aggiunge un metodo readData della classe
Impiegato. Per esempio:

o Dinamico: void readData(BufferedReader in) throws IOException { . . . }


i.readData(in);

o Statico: Impiegato[] readData(BufferedReader in) throws IOException { . . . }


Impiegato[] staff = Impiegato.readData(in);
Stream di oggetti
L’utilizzo di un formato dei record a lunghezza fissa è una buona scelta nei casi in cui sia necessario
memorizzare dati dello stesso tipo. Tuttavia, gli oggetti che si creano in un programma orientato
agli oggetti sono raramente tutti dello stesso tipo. Per esempio si può avere un array di nome staff
che è formalmente un array di record Impiegato ma che contiene in effetti oggetti che sono
istanze di una sottoclasse, come Manager.

 Se si vogliono memorizzare i file che contengono questo genere di informazioni, si deve


prima memorizzare il tipo di ogni oggetto e poi i dati che definiscono lo stato corrente
dell’oggetto.

 Quando si devono rileggere queste informazioni da un file, è necessario procede come


segue:

o Si legge il tipo di oggetto

o Si crea un oggetto vuoto di questo tipo

o Si riempie l’oggetto con i dati memorizzati nel file

Sun Microsystems ha sviluppato un meccanismo potente che permette di eseguire queste


operazioni con uno sforzo minimo, che prende il nome di serializzazione degli oggetti e rende
praticamente automatico ciò che in precedenza rappresentava una procedura decisamente
laboriosa.

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.

La classe StringBuilder è stata introdotta da JDK 5.0. La classe precedente, StringBuffer, è


leggermente meno efficiente, anche se permette a thread multipli di aggiungere o rimuovere
caratteri. Se le modifiche delle stringhe si verificano in un thread singolo, conviene utilizzare al suo
posto StringBuilder. Le API delle due classi sono identiche.
Memorizzare oggetti di tipo variabile

 Per memorizzare i dati geli oggetti e prima necessario aprire un oggetto


ObjectOutputStream:

ObjectOutputStream out = new ObjectOutputStream(new


FileOutputStream(“impiegato.dat”));

 A questo punto per memorizzare un oggetto si utilizza il metodo writeObject della classe
ObjectOutputStream, come indicato nella porzione di codice che segue:

Impiegato harry = new Impiegato(“Harry Hacker”, 5000, 1989, 10, 1 );


Manager boss = new Manager(“Carl Cracker”, 8000, 1987, 12, 15);
out.writeObject(harry); out.writeObject(boss);

 Per leggere nuovamente gli oggetti si deve prima ottenere un oggetto


ObjectOutputStream

ObjectInputStream in = new ObjectInputStream(new


FileInputStream(“impiegato.dat”)

 Per recuperare gli oggetti nello stesso ordine con il quale sono stati scritti si utilizza il
metodo readObject:

Impiegato e1 = (Impiegato) in.readObject();


Impiegato e2 = (Impiegato) in.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:

class Employee implements 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:

Impiegato [] staff = new Impiegato[3];


...
out.writeObject(staff);

 Analogamente anche la lettura viene effettuata con una sola operazione applicando un
cast al valore restituito:

Impiegato [] staff = (Impiegato []) in.readObject();

Utilizzare la serializzazione per clonare


Esiste un utilizzo del meccanismo di serializzazione che si rileva a volte utile: si mette a
disposizione un modo semplice per clonare un oggetto, a condizione che la classe sia serializzabile.

 Per clonare un oggetto serializzabile, è sufficiente serializzarlo in uno stream di output e


rileggerlo. Il risultato è un nuovo oggetto che rappresenta una copia profonda dell’oggetto
esistente.

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.

Gestione dei file


La gestione dei file riguarda operazioni diverse dalla semplice lettura e scrittura. La classe File
incapsula le funzionalità necessarie per lavorare con il file system del computer dell’utente. Per
esempio, si utilizza la classe File per scoprire quando è stato modificato il file per l’ultima volta
oppure è stato spostato o rinominato. In altre parole, le classi stream riguardano il contenuto del
file, mentre la classe File ha a che fare con la memorizzazione del file su disco.

 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:

File f = new File(“test.txt”);

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)

 Infine nel costruttore si può utilizzare un oggetto File esistente:

File(File dir, 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:

File tempDir = new File(File.separator + “temp”);

 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.

Potrebbero piacerti anche