Indice
Indice
1
1
Java I/O API
1.1 Flussi di byte in input/output . . .
1.2 Leggere e scrivere su file . . . . .
1.3 Flussi di caratteri in input/output .
1.4 Serializzazione/Deserializzazione
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1
2
4
7
10
Java I/O API
Il sistema I/O: questioni generali
• Creare un buon sistema I/O è uno dei compiti più difficili per i progettisti di linguaggi
• Questo fatto è evidenziato dal numero di differenti approcci
• La sfida pare essere quella di volere coprire tutte le eventualità ...
– riguardo ai diversi tipi di sorgenti: memoria centrale, files, la console, connessioni di rete, ecc.
– ma anche riguardo le diverse modalità di comunicazione: sequenziale, accesso casuale, bufferizzata,
binaria, a caratteri, per linee, per parole (word), ecc.
• Java presenta molte classi (il package java.io), la cui somma è (solo) apparentemente confusa, in realtà
contiene l’esplosione che il sistema di I/O potrebbe comportare
Flussi di Input/Output (I/O Stream)
Per ricevere in ingresso dei dati, un programma apre uno stream su una sorgente di informazioni (file, memoria,
connesioni di rete) e ne legge sequenzialmente le informazioni
lettura
Applicazione
I N
F O
R M
A Z
I O
N I
Sorgente
Analogamente un programma può inviare informazioni ad un destinatario, aprendo uno stream verso di esso e
scrivendo sequenzialmente le informazioni in uscita
scrittura
Applicazione
I N
F O
R M
A Z
I O
N I
Destinazione
Reading e Wrinting
Il processo di lettura (Reading) di informazioni può essere sintetizzato come segue:
open(stream)
while (more information)
read(information)
close(stream)
Similmente per il processo di scrittura (Wrinting):
open(stream)
while (more information)
write(information)
close(stream)
Flussi di byte e di caratteri
Il pacchetto java.io è composto da classi per la gestione di flussi di byte e classi per la gestione di flussi di
caratteri
• Flusso di byte (byte stream) I byte sono sequenze di 8-bit (come da definizione); in questo caso si parla di I/O
binario e viene usato in generale per i dati (es. i bit di un’immagine digitale o di un segnale sonoro digitalizzato).
I flussi di byte sono divisi in flussi d’ingresso (InputStream) e flussi d’uscita (OutputStream)
• Flusso di caratteri (character stream) Flussi di caratteri Unicode a 16-bit; parliamo allora di I/O testuale (es. i
caratteri ascii). I flussi di caratteri sono divisi in lettori (Reader) e scrittori (Writer)
1.1
Flussi di byte in input/output
I flussi di byte
• Uno stream è un canale di comunicazione collegato ad una sorgente o destinazione attraverso il quale le unità
base di informazione (in questo caso i byte) viaggiano in sequenza
• Le varie classi che realizzano gli stream di byte si distinguono per il tipo di sorgente o destinazione con cui possono dialogare. Sorgenti diverse, che sono legate a problemi di implementazione o tecnologia diverse richiedono
implemetazioni diverse
• I metodi per la lettura e scritture di byte sono comunque gli stessi. In java.io sono definite due classi astratte InputStream e OutputStream che definiscono l’interfaccia per gli stream di input e output
rispettivamente
Flussi di byte in input: gerarchia
FileInputStream
LineNumberInputStream
PipedInputStream
DataInputStream
<<abstract>>
FilterInputStream
BufferedInputStream
ByteArrayInputStream
<<abstract>>
InputStream
PushBackInputStream
SequenceInputStream
StringBufferInputStream
ObjectInputStream
2
I canali di input
Canali tipici:
• Un array di byte (ByteArrayInputStream)
• Un oggetto String (StringBufferInputStream)
• Un file (FileInputStream)
• Una pipe che realizza lo scambio tra processi (thread) (PipedInputStream)
• Una sequenza da altri stream collettati insieme in un singolo stream (SequenceInputStream)
• Altre sorgenti, come le connessioni ad Internet
In aggiunta la classe astratta FilterInputStream fornisce utili modalità di input per dati particolari come i tipi
primitivi, meccanismi di bufferizzazione, ecc.
La classe astratta InputStream
Dichiara i metodi che permettono di leggere i byte di una sorgente specificata.
• public abstract int read() throws IOException Legge un singolo byte e lo restituisce sotto
forma di intero (tra 0 e 255). Se non è disponibile (fine del flusso) viene restituito -1.
• public int read(byte[] buf, int offset, int count) throws IOException Legge una
sequenza di byte e la memorizza in un array di byte. Il numero massimo di byte letti è count. I byte vengono
memorizzati a partire da buff[offset] sino a un massimo di buff[offset+count-1]. Restituisce -1
se si è giunti alla fine del flusso e 0 se vengono letti zero byte.
I metodi di InputStream
• public long skip(long count) throws IOException Avanza lungo il flusso per un numero di
byte di input pari a count. Restituisce l’effettivo numero di byte saltati *
• public void close() throws IOException Chiude il flusso di input. Andrebbe invocato per rilasciare le risorse (es. i file) associate allo stream. Una volta invocato questo metodo, ulteriori operazioni
sollevano eccezioni. L’implementazione fornita da InputStream non fa nulla *
* Le sottoclassi dovrebbero fornire un’implementazione più efficiente
Flussi di byte in output: gerarchia
FileOutputStream
<<abstract>>
FilterOutputStream
<<abstract>>
OutputStream
DataOutputStream
BufferedOutputStream
PrintStream
ByteArrayOutputStream
PipedOutputStream
ObjectOutputStream
3
I canali di output
Canali tipici:
• ByteArrayOutputStream: crea un buffer in memoria e invia dati al buffer
• FileOutputStream: per inviare informazioni a un file
• PipedOutputStream: implementa il concetto di pipe
• ObjectOutputStream: per inviare oggetti al destinatario
In aggiunta la classe astratta FilterOutputStream fornisce utili modalità di output per dati particolari come i tipi
primitivi, meccanismi di bufferizzazione, ecc.
La classe astratta OutputStream
Fornisce un’astrazione per scrivere i byte in una certa destinazione. I metodi sono:
• public abstract void write(int b) throws IOException Scrive b sotto forma di byte. Il
byte viene passato come argomento di tipo int perché spesso è il risultato di un’operazione aritmetica su un
byte (si evitano cosı̀ conversioni di tipo veso il byte). Tuttavia solo gli 8 bit meno significativi del numero
intero vengono scritti
• public void write(byte[] buf, int offset, int count) throws IOException Scrive una parte di array di byte, a partire da buff[offset] fino a un numero pari a count
I metodi di OutputStream
• public void flush() throws IOException Effettua il flush (svuotamento) del flusso
• public void close() throws IOException Chiude il flusso di output. Andrebbe invocato per rilasciare le risorse (es. i file) associate allo stream. Una volta invocato questo metodo, ulteriori operazioni
sollevano eccezioni. L’implementazione di default non fa nulla *
* Le sottoclassi dovrebbero fornire un’implementazione più efficiente
1.2
Leggere e scrivere su file
Flussi di byte da e verso file
Per scrivere su un file dovremo far cooperare:
• Un oggetto che crea fisicamente un collegamento con il file (vedi la classe File)
• Un oggetto che gestisce lo stream (il canale di comunicazione verso il file) e che è in grado di inviare byte lungo
lo stream (sarà un’istanza di una sottoclasse di OutputStream)
In seguito vedremo anche come sia possibile “spezzare” informazioni complesse (ad esempio String, double o
int) in singoli byte tali da poter essere inviati sullo stream
Collegamento ad un file
La classe File fornisce una rappresentazione astratta ed indipendente dal sistema dei pathname gerarchici (list,
permessi, check esistenza, dimensioni, tipo, crea dir, rinomina, elimina)
• Rappresenta solo il nome di un particolare file o il nome di gruppi di file in una directory
• Gli oggetti di tipo File consentono di creare un collegamento con il file fisico
• Si ricordi che le interfacce utente e i sistemi operativi utilizzano pathname dipendenti dal sistema per attribuire
un nome ai file e alle directory
4
Pathname
• Rappresentazione dei path nei sistemi Unix e Windows:
Unix:
/home/grossi/labprog/lez08/java/Prova.java
Windows:
c:\home\grossi\labprog\lez08\java\Prova.java
• La convenzione di Java è quella di rappresentare i file nel formato di Unix, ma l’interpretazione è dipendente
dal sistema sottostante
Utilizzato in ambiente Windows, la barra / viene interpretata come \
File: creazione e cancellazione
• public boolean createNewFile() throws IOException Crea un nuovo file vuoto con il nome
specificato se e solo se tale file non esiste
• public boolean mkdir() Crea una directory con il nome denotato dal nome astratto
• public boolean delete() Cancella il file o la directory denotati dal nome astratto. Nel caso di directory, allora viene cancellata solo a patto che sia vuota
File: metodi di test
• public boolean exists() Restituisce true se e solo se il file rappresentato dal nome astratto esiste
• public boolean setReadOnly() Rende il file o la directory accessibili solo in lettura. Restituisce
true se e solo se l’operazione ha successo
• public boolean canWrite() Restituisce true se e solo se il file denotato dal nome astratto esiste e
può essere scritto
• public boolean canRead() Restituisce true se e solo se il file denotato dal nome astratto esiste e può
essere letto
File: informazioni
• public String getName() Restituisce il nome del file (o della directory) denotato dal nome astratto. È
solo l’ultimo nome nella nel pathname globale. Se il pathname è vuoto viene restituita la stringa vuota
• public String getPath() Restituisce il pathname completo del file
• public long lastModified() Restituisce un long che rappresenta la data dell’ultima modifica
• public long length() Restituisce la dimensione in bytes del file denotato dal nome astratto
File: esempio 1
List directory
Costruire la classe LsDir che si comporti come il comando Unix list directory: legge il contenuto di una directory
specificata come argomento e stampa in output l’elenco ordinato dei nomi di file e dirs presenti nella dir in esame. Se
questa non viene specifcata, si considera la dir corrente (.).
(Sugg.: utilizzare il metodo list() della classe File)
5
File: esempio 2
List directory
Costruire la classe MkDir che si comporti come il comando Unix make directory: crea directory con i nomi specificati
come argomento. Inoltre se viene data l’opzione -r e due nomi (argomenti), rinomina la prima dir (se esiste) col
secondo nome.
Usage: MkDir path1 ... - Crea una o più dir
Usage: MkDir -r path1 path2 - Muove una dir nell’altra
Scrivere e leggere da file
• In FileInputStream tutti i metodi di InputStream sono sovrascritti. Inoltre ha il costruttore:
– public FileInputStream(File file) Crea un FileInputStream aprendo una connessione al File specfificato
• In FileOutputStream tutti i metodi di OutputStream sono sovrascritti
– public FileOutputStream(File file) Crea un FileOutputStream aprendo una connessione al File specfificato
Leggere e scrivere su file
Copiare file
Costruire la classe CpFile che effettui la copia di un file in un altro già esistente (sovrascrivendolo). Se il file
destinatario non esiste solleva l’eccezione:
FileNotFoundException("Il file: "+args[0] ...
Sugg.: Okkio alle eccezioni!!!
Uso: java CopiaFile <file_da_copiare> <file_copia>
Scrivere dati primitivi su stream di byte
Per scrivere dati primitivi in un file di testo avremo bisogno di una classe che consenta di convertire tali dati in sequenze di byte. La classe PrintStream, che è un’estensione FilterOutputStream, fornisce queste
funzionalità
• public PrintStream(OutputStream out) Crea un print stream verso l’output stream specificato
• I metodi che mette a dispozione sono i tipici metodi di scrittura: void print(boolean b), void print(char
c), void print(char[] s), void print(double d), void print(float f), void print(int
i), void print(long l), void print(Object obj), void print(String s)
• La corrispondente versione con println
6
La classe DatiPrimitivi
import java.io.*;
public class DatiPrimitivi {
public static void main(String[] args) throws IOException {
File file = new File("prova.dat");
FileOutputStream fos = new FileOutputStream(file);
PrintStream ps = new PrintStream(fos);
ps.println("Stringa");
ps.println(100);
ps.println(3 / 4.0);
ps.println(’q’);
ps.println((byte) 128);
ps.println(new char[] { ’C’, ’I’, ’A’, ’O’ });
ps.println(true && false);
}
}
I flussi di I/O Standard
I concetti di standard input, standard output e standard error sono ereditati dalle librerie C ed introdotti nell’ambiente Java come campi statici della classe System
• public static final InputStream in Lo standard input System.in è usato per l’input al programma, tipicamente (ma non obbligatoriamente) legge l’input da tastiera
• public static final PrintStream out Lo standard output System.out è usato per l’output del
programma tipicamente (ma non obbligatoriamente) mostrato a video
• public static final PrintStream err Lo standard error System.err ha la funzione di mostrare a video messaggi di errore
1.3
Flussi di caratteri in input/output
Flussi di caratteri in input: gerarchia
StringReader
CharArrayReader
InputStreamReader
FileReader
<<abstract>>
Reader
<<abstract>>
FilterReader
PushbackReader
PipedReader
BufferedReader
LineNumberReader
La classe astratta Reader
Fornisce metodi analoghi a quelli della classe InputStream:
• public int read() throws IOException Legge un singolo carattere e lo restituisce sotto forma di
intero (tra 0 e 65535). Se non è disponibile (fine del flusso) viene restituito -1
7
• public abstract int read(char[] buf, int offset, int count) throws IOException
Legge una sequenza di caratteri e la memorizza in un array di char ...
• public int read(char[] buf) throws IOException Come public int read(buf, 0,
buf.length)
• public boolean ready() throws IOException Restituisce true se il flusso è pronto per la lettura (esiste almeno un carattere disponibile)
InputStreamReader
Un’oggetto di questa classe costituisce un ponte fra uno stream di byte e uno stream di caratteri. Il costruttore
• InputStreamReader(InputStream in) Crea un InputStreamReader che usa la codifica dei caratteri di default del sistema
InputStreamReader è una sottoclasse di Reader quindi mette a disposizione i metodi per leggere caratteri
FileReader
Classe di utilità per leggere file di caratteri. Il costruttore assume che il file contenga caratteri nello schema di
codifica standard. E’ un’estensione di InputStreamReader
Costruttori:
• public FileReader(File file)
• public FileReader(String nomefile)
Problema
Contare gli spazi
Definire una classe che conta gli spazi di un file di testo specificato come argomento o, in mancanza di argomento,
dallo standard input.
Flussi di caratteri in output: gerarchia
StringWriter
CharArrayWriter
OutputStreamWriter
<<abstract>>
<<abstract>>
Writer
FilterWriter
PipedWriter
BufferedWriter
PrintWriter
8
FileWriter
La classe astratta Writer
Fornisce un’astrazione per scrivere i caratteri in una certa destinazione
• public void write(int c) throws IOException Scrive c sotto forma di carattere. Il carattere
viene passato come argomento di tipo int, tuttavia solo i 16 bit meno significativi vengono scritti
• public abstract void write(char[] buf, int offset, int count) throws IOException
Scrive una porzione di array di caratteri ...
• public void write(String s, int offset, int count) throws IOException Scrive un
numero di caratteri pari a count della stringa s a partire da s.charAt(offset).
Copiare file di testo
Copiare se stessi
Scrivere un programma (classe Copy.java) che usa FileReader e FileWriter per copiare se stesso in un file
di backup (CopyBkup.java)
Lavorare con i Filtri
Il pacchetto java.io fornisce un insieme di classi che permettono di concatenare dei flussi allo scopo di produrne
dei nuovi e maggiormente utili. I dati vengono filtrati nell’atto in cui vengono letti e scritti da uno stream
Esempio:
Possiamo collegare un filtro allo standard input, come segue:
BufferedReader d = new BufferedReader(
new DataInputStream(System.in));
String str;
while ((str = d.readLine()) != null) {
// fai qualcosa ...
}
Qui BufferedReader memorizza i dati in un buffer evitando ripetute letture allo stream
BufferedReader
Legge un testo da un stream di input a caratteri, bufferizza i caratteri letti in modo da fornire strumenti efficienti
per leggere caratteri, array e linee. Consente di trattare in lettura direttamente delle stringhe
• public String readLine() throws IOException Legge una linea di testo. Un linea è considerata conclusa dai caratteri ’\n’ (line feed), a ’\r’ (carriage return), o da un carriage return seguito da un
linefeed.
Se non è stata raggiunta la fine dello stream restituisce la stringa contente la linea di caratteri letta ma che non
include i caratteri di terminazione. Se è stata raggiunta la fine dello stream restituisce null
Leggere linee da un file di testo
// collegamento al file
File file = new File(nomefile);
// stream di caratteri
FileReader fr = new FileReader(file);
// lettore bufferizzato
BufferedReader br = new BufferedReader(fr);
String riga;
9
while ((riga = br.readLine()) != null )
System.out.println(riga);
Leggere linee dallo standard input
Avevamo osservato che System.in è di tipo InputStream. Allora un possibile modo di leggere linee di testo
dallo standard input è il seguente
// per convertire stream di byte in strem di car.
InputStreamReader isr =
new InputStreamReader(System.in);
// lettore bufferizzato
BufferedReader br = new BufferedReader(isr);
String riga;
while ((riga = br.readLine()) != null )
System.out.println(riga);
Buffering ...
Echo dell’input
Scrivere la classe Echo.java che legge righe di testo da stdin e, dopo aver digitato return, le ristampa su stdout.
1.4
Serializzazione/Deserializzazione
Scrivere e leggere oggetti ...
• In molte applicazioni risulta utile scambiare oggetti in formato binario, senza passare attraverso una conversione
del formato.
• Java fornisce a questo scopo degli strumenti per scrivere su uno stream di byte e leggere da uno stream byte
direttamente degli oggetti.
• Ogni classe che implementa l’interfaccia Serializable può essere trasformata in una sequenza di byte che
può essere successivamente ricomposta a formare l’oggetto originale
... scrivere e leggere oggetti
• Serializzazione: il processo che consistente nel convertire la rappresentazione di un oggetto in un flusso di byte.
• Deserializzazione: il processo di ricostruzione di un oggetto a partire da un flusso di byte
Questo meccanismo è applicabile anche inviando i byte attraverso la rete. Il meccanismo di serializzazione compensa
in modo automatico alle differenze di rappresentazione che possono esistere fra diversi sistemi operativi.
Ad esempio, è possibile creare un oggetto su una macchina Windows, serializzarlo, inviarlo attraverso la rete a una
macchina Unix dove sarà correttamente ricostruito.
Le classi
• ObjectOutputStream fornisce metodi per convertire un oggetto o un dato primitivo in una sequenza di
byte per poi inviarlo lungo un OutputStream
• ObjectInputStream fornisce metodi per convertire una sequenza di byte proveniente da un InputStream
in un oggetto o in dato primitivo
10
ObjectOutputStream
Costruttore:
• public ObjectOutputStream(OutputStream out) throws IOException Crea un ObjectOutputStream
che scrive sull’OutputStream specificato
Metodi:
• public final void writeObject(Object obj) throws IOException Scrive uno specifico
oggetto sull’ObjectOutputStream
• Metodi per scrivere dati primitivi: writeDouble(double data), writeFloat(float data), writeInt(int
data), writeLong(long data), ...
ObjectInputStream
Costruttore:
• public ObjectInputStream(InputStream in) throws IOException Crea un ObjectInputStream
che legge dall’InputStream specificato
Metodi:
• public final Object readObject() throws OptionalDataException, ClassNotFoundException,
IOException Legge un oggetto dall’ObjectInputStream
• Metodi per leggere dati primitivi: double readDouble(), float readFloat(), int readInt(),
long readLong() ...
Scrivere oggetti
FileOutputStream out =
new FileOutputStream("dataOdierna");
ObjectOutputStream s = new ObjectOutputStream(out);
s.writeObject("Data di oggi: ");
s.writeObject(new Data());
s.flush();
La serializzazione va richiesta
Attenzione: gli oggetti non sono automaticamente serializzabili
• Un oggetto può contenere dati che non si vuole siano accessibli dall’esterno (come una password).
• Una volta serializzata e, ad esempio memorizzata su file, tale informazione può essere recuperata anche se nella
classe originale era dichiarata privata
• Per poter scrivere (o leggere) gli oggetti di una classe questa deve dichiarare esplicitamente di ammettere la
serializzazione implementando l’interfaccia marker Serializable
Serializable
• Si tratta di un’interfaccia vuota!
• Non contiene nessun metodo, per questo motivo viene anche detta classe marcatrice (marker)
• Per rendere una classe serializzabile è sufficiente dichiarare che implementa l’interfaccia Serializable; non
occorre implementare alcun metodo
11
import java.io.Serializable;
public class MiaClasse implements Serializable {
...
}
Esempio:
public final class String implements Serializable
Leggere oggetti
Il seguente codice legge gli oggetti scritti nello stream object dell’esempio precedente (deve essere collegato a un
InputStream)
FileInputStream in =
new FileInputStream("dataOdierna");
ObjectInputStream s = new ObjectInputStream(in);
String oggi = (String)s.readObject();
Date date = (Date)s.readObject();
Deserializzazione: esperimento
File: Alieno.java
import java.io.Serializable;
public class Alieno implements Serializable {}
File: ScriviAlieno.java
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.IOException;
public class ScriviAlieno {
public static void main(String[] args) throws IOException {
FileOutputStream fos=new FileOutputStream("fileAlieno.bin");
ObjectOutputStream oos = new ObjectOutputStream(fos);
Alieno et = new Alieno();
oos.writeObject(et);
}
}
Leggiamolo...
Compiliamo ed eseguiamo: viene generato il file binario fileAlieno.bin
File: LeggiAlieno.java
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.IOException;
public class LeggiAlieno {
public static void main(String[] args) throws Exception {
FileInputStream fis=new FileInputStream("fileAlieno.bin");
ObjectInputStream ois = new ObjectInputStream(fis);
Object alieno = ois.readObject();
System.out.println(alieno.getClass());
}
}
12
Esecuzione
Leggiamolo ora in una directory che non contiene la classe Alieno
> java LeggiAlieno
Exception in thread "main" java.lang.ClassNotFoundException: Alieno
at java.net.URLClassLoader$1.run(URLClassLoader.java:200)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:188)
at java.lang.ClassLoader.loadClass(ClassLoader.java:297)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:286)
at java.lang.ClassLoader.loadClass(ClassLoader.java:253)
at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:313)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:195)
at java.io.ObjectInputStream.resolveClass(ObjectInputStream.java:654)
at java.io.ObjectInputStream.inputClassDescriptor(ObjectInputStream.java:918)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:366)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:236)
at java.io.ObjectInputStream.inputObject(ObjectInputStream.java:1186)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:386)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:236)
at LeggiAlieno.main(LeggiAlieno.java:11)
Quindi ...
• Per poter recuperare la struttura dell’oggetto una volta letto necessario che la JVM possa accedere a tutte le
classi che sono state coinvolte nel processo di serializzazione.
• Nel caso precedente ponendo la classe compilata Alieno.class nella directory in cui è eseguita LeggiAlieno
si ottiene:
> java LeggiAlieno
class Alieno
13