appunti java
pag.112
9. Input e output
Nell’informatica “classica” il concetto di input e output era strettamente legato
all’uso dei dispositivi esterni di memorizzazione (dischi, nastri) e quindi al
concetto di file, al più veniva esteso alla tastiera come dispositivo di input e alla
stampante “pensata” come file di output. La tendenza odierna è quella di separare
nettamente i dispositivi dal trattamento dei flussi di dati da e verso di essi. Per
questa ragione in Java e in C++ si è fatta la scelta di introdurre “astrazioni”
pensate come “oggetti” che sono indipendenti dai dispositivi. Si parla quindi di
oggetti Stream, Pipe o Filter che sono veri e propri Abstract Data Type dotati di
operazioni non dipendenti dai dispositivi.
9.1. Stream, filter, pipe di dati
Il significato letterale della parola stream, dal dizionario di inglese, è quello di
“fiume”, “torrente” o “flusso”. Nel caso dell’informatica il flusso è un’astrazione
che definisce le modalità (i metodi) di lettura dei “token” di informazione che
fluiscono nello stream di input o dei metodi di scrittura in uno stream di output.
Un token è una unità di informazione accessibile è può consistere in un byte, un
carattere o un oggetto strutturato.
Uno stream o flusso di dati o canale è un percorso di comunicazione tra la
sorgente di una certa informazione e la sua destinazione.
Flusso
Destinazione
Sorgente
Le sorgenti possono essere le più svariate: la memoria Ram del computer,
Internet, un dispositivo permanente di memorizzazione dati, la tastiera ecc. La
stessa cosa vale per le destinazioni.
La sorgente e la destinazione del flusso di byte sono anche denominati
rispettivamente processo produttore e processo consumatore. Non è necessario
conoscere in dettaglio la sorgente o la destinazione per leggere un flusso di input
o per scrivere in un flusso di output.
I metodi generali (oppure i programmi) che operano su una sorgente di dati
accettano come argomento un flusso di input, così quelli che scrivono su una
destinazione accettano come argomento uno stream di output.
Flusso di input
Sorgente
Metodo o
programma
che legge dati
Metodo o
programma
che scrive dati
Flusso di output
Destinazione
appunti java
pag.113
Oltre ai metodi (o programmi) che fanno uso di stream di I/O esistono anche
“oggetti” detti processori di dati.
Un filter (filtro) o processore è un metodo, o un programma, dotato di due
argomenti di tipo flusso: legge dati dal flusso di input, li elabora (filtra, seleziona
ecc.) e li scrive sul flusso di output. I filtri non necessitano di alcuna
informazione, nè riguardo alla sorgente nè riguardo alla destinazione; i filtri
richiedono soltanto le modalità di accesso (astratte) al rispettivo flusso. Un filtro
può essere rappresentato dalla figura:
Flusso di
INPUT
Filtro o
Processore
Flusso di
OUTPUT
Infine esiste un oggetto particolare che prende il nome di pipe o condotto o
convogliatore. Il suo ruolo è quello di connettere e sincronizzare un flusso di
input con un flusso di output. Non si occupa di trasformare il contenuto dei
dati come il filtro.
Flusso di
INPUT
Flusso di
OUTPUT
Pipe o “condotto”
Queste astrazioni:
♦ Sorgente o processo produttore dei dati;
♦ Flusso di input;
♦ Pipe o condotto;
♦ Filtro o processore;
♦ Flusso di output;
♦ Destinazione o processo Consumatore dei dati
consentono di separare logicamente quelle entità, come File e dispositivi di
memorizzazione, che nell’informatica “classica” erano considerate legate in modo
univoco. È il programmatore che decide quali relazioni costruire tra le diverse
entità astratte e tra queste ultime e i dispositivi concreti.
9.2. Flussi di input e output in Java
Nel package java.io sono contenute le classi che consentono di gestire le
operazioni relative ai flussi. InputStream e Reader sono flussi astratti di input e
sono genitori di tutte le classi di input di Java.
InputStream (e le sue sottoclassi) tratta flussi di Byte e definisce i modi
fondamentali con cui si possono “consumare”.
Reader (e le sue sottoclassi) tratta flussi di Caratteri e definisce i modi
fondamentali con cui si possono “consumare”.
Visto che i genitori sono classi astratte, i relativi metodi sono implementati solo
nelle sottoclassi della gerarchia.
appunti java
pag.114
Gli schemi seguenti mostrano la gerarchia, anche se non completa, delle classi
essenziali relative al trattamento dei flussi di input
InputStream
ByteArrayInputStream
FileInputStream
Reader
CharArrayReader
StringReader
InputStreamReader
BufferedReader
FileReader
Di seguito si riporta la gerarchia, simmetrica a quella di input, delle classi relative
ai flussi di output.
OutputStream
ByteArrayOutputStream
FileOutputStream
Writer
CharArrayWriter
StringWriter
OutputStreamWriter
BufferedWriter
FileWriter
In prevalenza saranno trattati flussi di tipo carattere (Reader e Writer); gli esempi
proposti potranno essere trasformati molto semplicemente per trattare flussi di
byte. poiché le sottoclassi relative ai flussi di tipo carattere sono più articolate di
quelle relative ai flussi di byte.
Dalla gerarchia si nota che il ByteArrayInputStream (sottoclasse di InputStream)
ha un corrispondendente in modalità carattere in CharArrayReader (sottoclasse
di Reader); analogo parallelo può essere fatto per i flussi di output.
Allo stesso modo la sottoclasse FileInputStream ha la corrispondente nella classe
FileReader (analogamente per l’output).
Oltre a CharArrayReader (Writer), in Reader (Writer) sono presenti le sottoclassi
StringReader (Writer), BufferedReader (Writer), InputStreamReader (Writer) che
rendono più ricca l'elaborazione dei flussi di caratteri.
Se si volesse spiegare sinteticamente il funzionamento di un flusso si dovrebbe
dire che su questo oggetto sono consentite poche e semplici operazioni:
♦ Creare un flusso di input che legge da una sorgente.
♦ Creare un flusso di output che scrive su una destinazione.
♦ Leggere un carattere (byte).
♦ Scrivere un carattere (byte).
♦ Leggere un gruppo di caratteri (byte) di dimensione nota.
appunti java
pag.115
♦ Scrivere un gruppo di caratteri (byte) di dimensione nota.
♦ Saltare (skip) in lettura un blocco di caratteri.
♦ Chiudere il flusso, liberando lo heap dagli oggetti costruiti.
Elenco sommario delle sottoclassi di I/O e dei costruttori e metodi principali.
CharArrayReader
CharArrayWriter
Costruttori CharArrayReader(char[])
Costruttori CharArrayWriter()
CharArrayReader(char[],int,int)
CharArrayWriter(int)
Metodi
Metodi
int read()
void write(int)
int read(char[],int,int)
void write(char[],int,int)
boolean ready()
void write(String)
long skip(long)
void write(String,int,int)
void close()
void close()
String toString()
char[ ] toCharArray()
StringReader
Costruttori
StringWriter
StringReader(String)
StringWriter()
StringWriter(int)
IDENTICI a CharArray
IDENTICI a CharArray più
Metodi
StringBuffer getBuffer()
InputStreamReader
Costruttori InputStreamReader(InputStream)
OutputStreamWriter
OutputStreamWriter(OutputStream)
Metodi
IDENTICI a CharArray
FileReader
Costruttori FileReader(String)
IDENTICI a CharArray
FileWriter
FileWriter(String)
FileWriter(String, boolean)
Metodi
IDENTICI inputStreamReader
BufferedReader
Costruttori BufferedReader(Reader)
BufferedReader(Reader, int)
Metodi
IDENTICI a CharArray più
String readLine()
[*]
IDENTICI outputStreamwriter
BufferedWriter
BufferedWriter(Writer)
BufferedWriter(Writer,int)
IDENTICI a CharArray più
void newLine()
Dalla tabella si nota che i metodi di ogni classe sono pochi e spesso identici. In
particolare si ripetono i metodi di lettura/scrittura di un byte, di un carattere o di
appunti java
pag.116
una sequenza di caratteri. Solo BufferedReader e BufferedWriter contengono i
metodi readLine() e newLine(), rispettivamente, che intuitivamente consentono di
agire non su singoli caratteri o su singoli byte, ma su intere righe.
9.3. Dalla sorgente alla destinazione
Si farà uso nel seguito di uno schema astratto, ma semplificato, che “pensa” ad
un programma che legge byte o caratteri come a un “processo consumatore”. Il
programma è immaginato come la destinazione del flusso di input. Si ricorda che
la distinzione tra inputStream e Reader si riferisce solo ai “token” che fluiscono;
nel primo si tratta esclusivamente di byte, nel secondo esclusivamente di
caratteri. Graficamente rappresenteremo la sorgente, il flusso e il programma
(destinazione del flusso) nel modo seguente:
Sorgente
Tipo di
Flusso
Flusso
Programma
Questa rappresentazione grafica consentirà di cogliere sinteticamente le entità
coinvolte e le loro relazioni reciproche.
Sorgente
array
X X X X
stringa
"XXXX"
Text
file
tastiera
Costruttore
Token del
flusso
Progr.
Destinazione
Flusso di Char
Programma.
Tipo di flusso
(cons. di Char)
Programma.
(cons. di Line)
Programma.
(cons. di Byte)
I simboli rappresentano:
• le sorgenti: array, stringa, file di testo, tastiera ecc;
• i tipi di flusso costruiti (classe Java utilizzata) per generare il flusso:
CharArrayReader
(CAR),
StringReader(SR),
InputStreamReader(ISR),
BufferedReader(BR), FileReader (FR);
• i componenti o token del flusso: Char, Line o Byte;
• il programma: consumatore dei Token del flusso.
appunti java
pag.117
Esempio 1. “Costruire un programma che consenta di consumare (leggere e
stampare sul monitor) un flusso di caratteri la cui sorgente è un array memorizzato
nella RAM.”
Lo schema che rappresenta il programma potrebbe essere il seguente.
Flusso di Char
X X X X
Programma.
(cons. di Char)
CAR
Nello schema sono indicati:
all'estrema sinistra, la sorgente che è un array di caratteri;
il costruttore CAR CharArrayReader del flusso, che ha come argomento un array;
il flusso ed i relativi token che in questo caso sono dei caratteri;
il Programma consumatore o destinatario.
Codifica:
import java.io.*;
public class cap09_es_01 {
public static void main(String args[])throws IOException
{
char[] c={'a','b','c','d','e','f','g'};
// sorgente
CharArrayReader f_in = new CharArrayReader(c); // costruzione flusso
int ch;
while ((ch=f_in.read())!=-1)
// ciclo di lettura
System.out.print((char)ch);
System.out.println("\nfine esecuzione.");
}
}
Esecuzione:
Commento al codice:
La classe CharArrayReader scelta per la risoluzione è quella più aderente alla
sorgente. Infatti il costruttore del flusso accetta come argomento un array di
caratteri. Il metodo base per acquisire i caratteri del flusso è read(). Quando con
successive letture si giunge al termine dello stream, read() restituisce il codice –1.
Se la sorgente non è un array di caratteri ma una stringa, il problema potrebbe
essere così riformulato:
Esempio 2. “Costruire un programma che consenta di consumare (leggere e
stampare sul monitor) un flusso di caratteri la cui sorgente è una stringa
memorizzato nella RAM.”
Flusso di Char
"XXXX"
SR
Programma.
(cons. di Char)
appunti java
pag.118
Codifica:
import java.io.*;
public class cap09_es_02 {
public static void main(String args[])throws IOException
{
String s="pippo e pluto";
// sorgente
StringReader f_in = new StringReader(s);
// costruzione flusso
int ch;
while ((ch=f_in.read())!=-1)
// ciclo di lettura
System.out.print((char)ch);
System.out.println("\nfine esecuzione.");
}
}
Esecuzione:
Commento al codice:
La classe StringReader, scelta per la risoluzione, è quella più aderente alla
sorgente. Infatti il costruttore del flusso accetta come argomento una stringa. Il
metodo base per acquisire i caratteri del flusso è ancora read() fino al termine
dello stream.
Gli esempi precedenti avevano solo un significato “didattico formale”. Infatti, se
l’obiettivo del programma è quello di stampare un array o una stringa, non è
necessario operare con uno stream, sarebbe sufficiente un banale programma di
stampa. L’esempio che segue è più significativo perché per la prima volta mostra
come si possano acquisire caratteri dalla tastiera.
Esempio 3. “Costruire un programma che consenta di consumare (leggere e
stampare sul monitor) un flusso di caratteri la cui sorgente è la tastiera”
Flusso di Char
ISR
Programma.
(cons. di Char)
import java.io.*;
public class cap09_es_03 {
public static void main(String args[])throws IOException
{
InputStreamReader f_in=new InputStreamReader(System.in);
int ch;
while ((ch=f_in.read())!='\r')
System.out.print((char)ch);
System.out.println("\nfine esecuzione.");
}
}
Commento al codice:
La tastiera è individuata in Java da System.in e si tratta, in particolare, di uno
stream di Byte. La classe InputStreamReader, scelta per la soluzione, è una
classe particolare che fa da ponte tra sorgenti di byte e sorgenti di caratteri.
In sostanza InputStreamReader converte una sorgente di byte in una sorgente di
caratteri. A conferma di questo si noti che il costruttore del flusso accetta come
appunti java
pag.119
argomento un oggetto di tipo InputStream (flusso di byte). Il metodo base per
acquisire i caratteri dal flusso è ancora read().
Si noti che in questo caso il ciclo di acquisizione di caratteri è interrotto (non dal
codice –1 che indica la fine dello stream) dal codice ‘\r’ che corrispondere alla
pressione del tasto di INVIO. Il programma acquisisce solo una riga di caratteri
fino alla pressione del tasto di invio. Se si volessero acquisire più righe, invece di
una sola, si potrebbe condizionare la fine dell’acquisizione alla lettura di un
carattere convenzionale come per esempio “#” nel modo seguente:
while ((ch=f_in.read())!='#')
invece di
while ((ch=f_in.read())!='\r')
in questo caso si acquisirebbero da tastiera tutte le righe desiderate fino a
quando non si immette il carattere convenzionale ‘#’.
Esempio 4. “Costruire un programma che consenta di consumare (leggere e
stampare sul monitor) un flusso di caratteri la cui sorgente è un file di testo
memorizzato su disco.”
Text
Flusso di Char
FR
Programma.
(cons. di Char)
Codifica:
import java.io.*;
public class cap09_es_04 {
public static void main(String args[])throws IOException
FileReader f_in = new FileReader("c:/mio.txt");
int ch;
while ((ch=f_in.read())!=-1) {
System.out.print((char)ch);
}
System.out.println("\nfine esecuzione.");
}
}
{
Esecuzione:
Il file, come mostra la finestra di output, contiene le cinque righe che sono
stampate prima del messaggio di <fine esecuzione.>
Commento al codice:
La classe FileReader, scelta per la soluzione, è quella più aderente alla sorgente.
Infatti il costruttore del flusso esige come argomento una stringa che deve
rappresentare il nome di un file su disco.
appunti java
pag.120
9.4. Flussi e Filtri
Fino a questo momento i quattro esempi sviluppati hanno utilizzato quattro delle
cinque classi di input illustrate nello schema del gruppo Reader. La quinta
BufferedReader più che una classe generatrice di flussi di input è un filtro; infatti
il suo costruttore esige come parametro un flusso di tipo Reader e costruisce un
flusso “bufferizzato” organizzato in linee. Uno schema che ne rappresenti il
funzionamento potrebbe essere il seguente:
flusso di caratteri o
Reader
Buffered
Reader
Flusso bufferizzato
in linee
Programma.
(cons. di Char o
Line)
Si nota che lo schema mostra che BuffereReader può essere pensato come il
generatore di un oggetto che ha come parametro di input un flusso e che
restituisce in output un flusso organizzato in linee che viene messo a disposizione
come flusso di input del programma consumatore. Questo risponde alla
definizione di Filtro in quanto da un flusso di caratteri si può ottenere un flusso
“modificato” organizzato in linee di testo con marcatori di fine linea (\r o \n).
esempio 5. “assegnata una sorgente di tipo array di caratteri che contenga anche
marcatori di fine linea (\r o \n), si desidera realizzare un programma che consumi i
dati in forma di flusso di input acquisendo le singole righe del testo e stampandole
numerate progressivamente.”
Lo schema mostra graficamente la relazione tra sorgente e flussi coinvolti:
Flusso di Char
X X X X
CAR
Flusso di Linee
BR
Programma.
(cons. di Linee)
Codifica:
import java.io.*;
public class cap09_es_05 {
public static void main(String args[])throws IOException
{
char[]c={'p',’r’,'i','m',’a’,'\n','s','e','c','o','n',’d’,’a’,'\n'};
CharArrayReader f_in = new CharArrayReader(c);
BufferedReader in=new BufferedReader(f_in);
String s=" "; int n=0;
while ((s=in.readLine())!=null) {
n=n+1;
System.out.println(n+”: “+s);
}
System.out.println("\nfine esecuzione.");
}
}
Esecuzione:
1: prima
2: seconda
fine esecuzione.
appunti java
pag.121
Commento al codice:
Notare che l’array contiene caratteri i marcatori, indifferentemente \r o \n.
Si costruisce prima il flusso di caratteri CharArrayReader che viene passato in
input (come parametro del costruttore) a BufferedReader che costruisce il flusso
Bufferizzato che sarà letto dal programma con il metodo readln() non presente
nelle altre classi flusso.
Il ciclo di lettura si interrompe quando la stringa in lettura è “null”.
Il programma precedente può essere di maggior interesse se applicato ad un file
di testo memorizzato su disco.
esempio 6. “realizzare un programma che legga un file di testo e stampi le singole
righe numerandole progressivamente.”
La figura evidenzia che si può costruire un flusso di caratteri utilizzando
FileReader e quindi filtrarlo con BufferedReader per ottenere il flusso di linee
desiderato.
Text
Flusso di Char
FR
Flusso di Linee
BR
Programma.
(cons. di Linee)
Codifica:
import java.io.*;
public class cap09_es_06 {
public static void main(String args[]) throws IOException
FileReader f_in = new FileReader("c:/mio.txt");
BufferedReader in=new BufferedReader(f_in);
String s=" ";int n=0;
while ((s=in.readLine())!=null) {
n=n+1;
System.out.println(n+”: “+s);
}
System.out.println("\nfine esecuzione.");
}
}
{
Commento al codice:
Il file stampato è identico a quello dell’esempio 4, la novità consiste nel fatto che
la condizione di fine ciclo è fondata, come quella dell’array precedente, sulla
presenza dei marcatori di fine linea nel file di testo e quindi sul fatto che
readLine() restituisce null.
Si costruisce prima il flusso di caratteri FileReader che viene passato in input
(come parametro del costruttore) a BufferedReader che costruisce il flusso
Bufferizzato che sarà letto dal programma con il metodo readln() non presente
nelle altre classi flusso.
appunti java
pag.122
9.5. Flussi di Output con le classi Writer
Si affronteranno ora alcuni esempi di utilizzo di flussi di output mostrando la
simmetria esistente rispetto ai flussi di input.
esempio 7. “realizzare un programma che generi in sequenza i caratteri ‘0’..’9’ e li
depositi su un flusso di output indirizzato ad una destinazione di tipo array di
caratteri. Al termine stampi l’array destinazione.”
Flusso di Char
Programma.
(prod. di Char)
CAW
X X X X
Codifica:
import java.io.*;
public class cap09_es_07 {
public static void main(String args[])throws IOException
{
CharArrayWriter out=new CharArrayWriter(); // (1) flusso do output
for (int i=0; i<=9; i++) {
String s=""+i;
// trasforma il numero in stringa
char ch=s.charAt(0);
// da stringa a carattere
out.write(ch);
// (2) scrive nel flusso
}
char[] c=out.toCharArray();
// (3) da flusso ad array di char
System.out.println("il flusso di output contiene:");
for (int i=0; i<c.length; i++)
System.out.print(c[i]);
System.out.println("\nfine esecuzione.");
}
}
Esecuzione:
il flusso di output contiene:
0123456789
fine esecuzione.
Commento al codice:
La nota (1) mostra il costruttore del flusso. La (2) il metodo di scrittura di un
carattere nel flusso di output. La (3) la trasformazione del flusso in un array
destinazione.
Si affronteranno due esempi più interessanti, il primo genera un file di testo in
output scrivendo su di esso alcune stringhe, il secondo lo genera acquisendo
l’input dalla tastiera.
esempio 8. “realizzare un programma che generi un file di testo inserendo su ogni
riga del file una parola contenuta in un array di stringhe.”
Lo schema grafico rappresentativo degli stream è il seguente:
appunti java
pag.123
Flusso di Char
Programma.
(prod. di Stringhe)
Text
FW
Codifica:
import java.io.*;
public class cap09_es_08 {
public static void main(String args[])throws IOException
{
FileWriter out=new FileWriter("c:/out.txt");
// (1)
String s[]={"pera","banana","fico","mela"};
for (int i=0; i<s.length; i++)
out.write(s[i]+'\n');
// (2)
out.close();
// (3)
/* verifica e stampa del file generato */
System.out.println("il flusso di output contiene:");
FileReader in=new FileReader("c:/out.txt");
int ch;
while ((ch=in.read()) != -1)
System.out.print((char)ch);
System.out.println("\nfine esecuzione.");
}
}
Esecuzione:
il flusso di output contiene:
pera
banana
fico
mela
fine esecuzione.
Commento al codice:
La nota (1) mostra il costruttore di FileWriter, la (2) la scrittura di una stringa sul
file seguita dal marcatore \n di “newline”. La nota (3) evidenzia la necessità di
chiudere il file di output per poter salvare i dati prima di riaprirlo per la lettura e
la stampa di verifica.
esempio 9. “realizzare un programma che generi un file di testo inserendo su ogni
riga una riga di testo acquisita da tastiera. Il programma termina quando da
tastiera si immette una riga vuota.”
Lo schema grafico rappresentativo degli stream è il seguente:
Flusso di Char
ISR
Flusso di Linee
BR
Flusso di Linee
Programma.
(cons. di Linee e
produtt. di linee)
Text
FW
BW
appunti java
pag.124
Codifica:
import java.io.*;
public class cap09_es_09 {
public static void main(String args[])throws IOException
{
InputStreamReader f_in=new InputStreamReader(System.in); //
BufferedReader in=new BufferedReader(f_in);
//
FileWriter f_out=new FileWriter("c:/out.txt");
//
BufferedWriter out=new BufferedWriter(f_out);
//
String s=" ";
while (s.length()!=0) {
// (5)
s=in.readLine();
// (6)
out.write(s);
// (7)
out.newLine();
// (8)
}
out.close();
// (9)
/* verifica e stampa del file generato */
System.out.println("\nil flusso di output contiene:");
FileReader new_in=new FileReader("c:/out.txt");
int ch;
while ((ch=new_in.read()) != -1)
System.out.print((char)ch);
System.out.println("\nfine esecuzione.");
}
}
(1)
(2)
(3)
(4)
Commento al codice:
Si tratta evidentemente di un programma di INPUT/OUTPUT in quanto dopo la
lettura di ogni linea dalla tastiera la trascrive nel file. Lo schema grafico mostra la
necessità di costruire un ISR flusso di caratteri, vedi nota (1) del codice. Si filtra il
flusso di input da tastiera attraverso un BR, nota (2) del codice, per ottenere un
flusso organizzato in linee. Le note (3) (4) mostrano il costruttore di FileWriter,
intermediario necessario per ottenere un output bufferizzato BW per la scrittura
di intere righe nel file. Le note (5),(6),(7) e (8) mostrano rispettivamente, la
condizione di interruzione della lettura da tastiera quando la riga di input è vuota
(s.length()==0), la lettura della linea di input dal BR, la scrittura della stringa sul
buffer del file e infine il newline necessario.
appunti java
pag. 125
9.6. Un filtro interessante StreamTokenizer
Un filtro molto utile per analizzare e selezionare “token” di uno stream di caratteri
è lo StreamTokenizer. Questo filtro consente di separare con estrema semplicità
diversi tipi di gettone (token) presenti nel flusso di input. Si possono isolare le
parole formate da soli caratteri alfabetici (WORD), i token numerici (NUMBER)
separandole da caratteri di punteggiatura o altro. Il filtro funziona identificando i
caratteri separatori che di norma sono SPAZI, fine linea EOL o fine file EOF.
Nella tabella sono riportatati sinteticamente campi, costruttori e metodi:
NOTE
StreamTokenizer
Costruttori
StreamTokenizer(Reader)
L’imput dello streamTokenizer e un oggetto Reader. In
particolare se si legge da tastiera si può usare un
InputStreamReader, se si legge da un file di testo è
necessario passare da FileReader ecc.
double nval
Campo che contiene il token di tipo NUMBER,
trasformato in double dopo l’isolamento del Token.
Dopo l’operazione di lettura a.nextToken(); se
a.ttype==a.TT_NUMBER allora si può acquisire il
valore double a.nval.
Campo che contiene, come nel caso precedente, il token
do tipo WORD.
La costante che indica che nextToken() ha incontrato il
fine file. Ad esempio if (a.ttype==a.TT_EOF)
Analogo al precedente.
Analogo al precedente.
Analogo al precedente.
Campo che contiene informazioni relativamente al tipo
di token isolato dopo una lettura a.nextToken(). E
possibile chiedersi SE
a.ttype==a.TT_WORD oppure stampare il carattere
(char)a.ttype
Attributi o Campi
String sval
static int TT_EOF
static int TT_EOL
static int TT_NUMBER
static int TT_WORD
int ttye
Metodi
void commentChar(char)
void quoteChar(int, int)
Definisce quali caratteri debbano essere interpretati come
un commento.
Decreta che il marcatore di fine linea deve essere
individuato e non tralasciato dal filtro.
Restituisce il numero di riga del token letto.
Se boolean è true, prima di analizzare i token li trasforma
tutti in lettere minuscole.
Legge il token successivo dello stream e restituisce un
intero che rappresenta il ttype (tipo di token).
Definisce il o l’intervallo dei caratteri ordinari che non
sono ne NUMBER ne WORD ne COMMENT ne
SPACE ne EOL ne EOF.
Definisce l’intervallo dei caratteri che appartengono a un
WORD. Per default sono i caratteri alfabetici.
Definisce i caratteri che identificano una costante stringa.
void whitespaceChars(int, int)
Definisce i caratteri che separano i Token.
void slashSlashComments(boolean)
void slashStarComments(boolean)
Definisce i caratteri che individuano commenti (che ST
tralascia nell’analisi) in C e C++ e Java /* */ oppure //
void eolIsSignificant()
int lineno()
void lowerCaseMode(boolean)
int nextToken()
void ordinaryChar(int)
void ordinaryChars(int, int)
void wordChars(int, int)
appunti java
pag. 126
esempio 10. “partendo da una stringa che contenga parole numeri e caratteri vari
filtrarla con uno StreamTokenizer per stampare in modo separato i vari token
indicando a fianco di ognuno di che cosa si tratta.”
Se utilizziamo gli schemi introdotti per indicare flussi e filtri si potrebbe indicare
la sequenza del programma con il seguente schema grafico:
Flusso di Char Flusso di Token
stringa
"XXXX"
SR
ST
Programma.
(cons. di Token)
Lo schema mostra che dalla stringa sorgente si deve costruire con StringReader
(SR) un flusso (Reader) necessario per costruire il filtro StringTokenizer (ST).
Questo restituisce in output un flusso filtrato di Token che il programma
consumerà per produrre gli effetti desiderati.
Codifica:
import java.io.*;
class cap09_es_10 {
public static void main(String arg[]) throws IOException {
String s="parola (1) sono un numero 2.55 ?? una costante ‘2001’";
StringReader f_char=new StringReader(s);
// (1)
StreamTokenizer st=new StreamTokenizer(f_char);
// (2)
st.nextToken();
// (3)
while (st.ttype!=st.TT_EOF) {
// (4)
if(st.ttype==st.TT_WORD)
// (5)
System.out.println("WORD:"+st.sval);
// (6)
else
if(st.ttype==st.TT_NUMBER)
// (7)
System.out.println("NUM:"+st.nval);
// (8)
else
if (st.sval==null)
// (9)
System.out.println("CHAR:"+(char)st.ttype);
// (10)
else System.out.println("OGGETTO di tipo "+st.ttype+" :"+st.sval); // (11)
st.nextToken();
}
}
}
Esecuzione:
WORD:parola
CHAR:(
NUM:1.0
CHAR:)
WORD:sono
WORD:un
WORD:numero
NUM:2.55
CHAR:?
CHAR:?
WORD:una
WORD:costante
OGGETTO di tipo 39:2001
appunti java
pag. 127
Commenti al codice:
Le note (1) e (2) mostrano la costruzione dei flussi indicati nello schema. La (3)
indica l’invocazione del metodo che stacca il primo Token. La nota (4) evidenzia
che il ciclo di estrazione dei Token ha termine quando si incontra il Token
TT_EOF (costante predefinita in StreamTokenizer che indica la fine del flusso).
Le note da (5) a (11) mostrano come si testano e si stampano i diversi token
utilizzando il campo ttype di ogni token che informa a quale tipo appartiene il
gettone attualmente in lettura . I tipi sono WORD, NUMBER, TT_EOF ecc. Se il
tipo è null significa che si tratta di un singolo carattere di punteggiatura.
Il metodo st.nextToken() presente nell’ultima riga del ciclo fa avanzare il
puntatore al prossimo tokem dello stream.
esempio 11. “Si desidera realizzare un programma che stampi l’intero file di testo
“c:/mio.txt” memorizzato su disco e successivamente tutti e solo i token WORD che
contiene”. Richieste:
♦ Passare il FileReader ad un BufferedReader al fine di stampare le righe del file; quindi
riaprire il FileReader e assegnarlo come input a StreamTokenizer per stampare i soli
WORD secondo lo schema grafico seguente. Lo schema mostra che pur trattandosi di
un unico codice questo può essere “pensato” come una coppia di programmi
indipendenti che nel caso specifico sono eseguiti una dopo l’altro. Il primo stampa
semplicemente il file di testo, il secondo mostra come vengono estratti dal file i soli
token di tipo WORD.
Text
Flusso di Char Flusso di Linee
FR
BR
Programma.
(cons. di Linee)
Flusso di Char Flusso di Token
FR
ST
Programma.
(cons. di Token)
Codifica:
import java.io.*;
class cap09_es_11 {
public static void main(String arg[]) throws IOException {
FileReader f_char=new FileReader("c:/mio.txt");
// (1)
BufferedReader f_line=new BufferedReader(f_char); // (2)
System.out.println("Le righe nel file sono:");
String s=" ";
while ((s=f_line.readLine())!=null)
System.out.println(s);
f_char=new FileReader("c:/mio.txt");
// (3)
StreamTokenizer st=new StreamTokenizer(f_char);
// (4)
System.out.println("\nLe sole parole sono:");
st.nextToken();
// (5)
while (st.ttype!=st.TT_EOF) {
if(st.ttype==st.TT_WORD)
System.out.print(st.sval+" ");
st.nextToken();
}
System.out.println("\nFine scansione.\n");
}
}
appunti java
pag. 128
Esecuzione:
Le righe nel file sono:
prima riga
numero 1234
anno "2001"
// commento non analizzato
NUMERO 3.32
Le sole parole sono:
prima riga numero anno NUMERO
Fine scansione.
Commenti al codice:
Le note (1) e (2), unite al primo ciclo di stampa. si riferiscono alla costruzione del
flusso di input FileReader (FR) che opera sul file di testo, questo flusso viene
filtrato dal successivo BufferedReader (BR) per consentire la stampa per linee del
testo e consumare il flusso.
Le note (3) (4) mostrano la sequenza di costruzione del FileReader che diviene
input di StreamTokenizer. Infine si avvia la lettura del primo token evidenziato
dalla nota (5). Le istruzioni successive consistono nel ciclo del programma che
consuma i token e stampa solo i WORD che sono quelli desiderati.
esempio 12. “Si desidera realizzare un programma che stampi tutti e soltanto i
token numerici di un file di testo memorizzato su disco con a fianco il numero di riga
del file su cui il token è scritto”.
Codifica:
import java.io.*;
class cap09_es_12 {
public static void main(String arg[]) throws IOException {
FileReader f_char=new FileReader("c:/mio.txt"); // (1)
StreamTokenizer st=new StreamTokenizer(f_char); // (2)
System.out.println("I numeri contenuti nel file sono:");
st.nextToken();
// (3)
while (st.ttype!=st.TT_EOF) {
if(st.ttype==st.TT_NUMBER)
System.out.println(st.nval+" riga:"+st.lineno());
st.nextToken();
}
System.out.println("\nFine scansione file.");
}
}
Esecuzione:
I numeri contenuti nel file sono:
1234.0 riga:2
3.32 riga:5
Fine scansione file.
Commenti al codice:
Il programma non differisce dalla seconda parte del precedente esempio 11 con le
uniche differenze che in questo caso si stampano solo i Token NUMBER e a fianco
il numero di riga su cui questi sono memorizzati. Il metodo st.lineno() è utilizzato
proprio al fine di estrarre i numeri di riga del token in esame.
appunti java
pag. 129
9.E – Esercizi
Problemi che usano stream o filtri in modo elementare. Uso delle sottoclassi di
ImputStream, OutputStream, Reader e Writer per risolvere problemi.
9.1.
Si desidera costruire un programma che “acquisisca da un array di
caratteri le singole lettere utilizzando un opportuno stream e stampi solo le
vocali”. Richieste: realizzare il solo main() scegliendo il corretto costruttore
di stream.
Nota: il metodo che testa se un carattere è contenuto nell’insieme delle
vocali potrebbe essere il seguente appartenente alla classe String.
String Voc=”aeiou”; char ch=’a’;
if ((Voc.indexOf(ch))!=-1)
9.2.
Si desidera costruire un programma che “acquisisca da una stringa le
singole lettere utilizzando un opportuno stream e le collochi in un
StringBuffer ponendo al termine di ogni sequenza di cinque caratteri il
marcatore ‘\n’ e infine stampi il buffer ottenuto”. Richieste: realizzare il
solo main() scegliendo il corretto costruttore di stream.
9.3.
Si desidera costruire un programma che “acquisita da tastiera una
sequenza di linee fino a quando non si immette il marcatore ‘#’. Trasferisca
le linee acquisite in un StringBuffer eliminando i marcatori di fine linea ‘\n’,
‘\r’; infine stampi il testo del Buffer ottenuto.” Richieste: realizzare il solo
main() scegliendo il corretto costruttore di stream.
9.4.
Si desidera costruire un programma che “acquisita dati da un array di
caratteri assegnato dall’utente, l’array contiene anche marcatori di fine linea
‘\n’; i dati acquisiti li scriva utilizzando un opportuno stream di output in un
file di testo memorizzato su disco.” Richieste: realizzare il solo main()
utilizzando il costruttore FileWriter per lo stream di output.
9.5.
Si desidera costruire un programma che “acquisita da una stringa
assegnata dall’utente i caratteri e li trascriva in sequenze di cinque caratteri
su ogni riga di un file di testo inserendo l’opportuno marcatore di fine linea.”
Richieste: realizzare il solo main() scegliendo il/i corretto/i costruttore/i di
stream di output.
9.6.
Si desidera costruire un programma che “acquisita da un file di testo
memorizzato su disco le diverse linee e le riscriva su un nuovo file di testo in
in sequenze di cinque caratteri su ogni riga di testo inserendo l’opportuna
marcatore di fine linea.” Richieste: realizzare il solo main() scegliendo i
corretti costruttori di stream di input e output.
♦ Problemi semplici che usano stream di I/O e la classe StreamTokenizer.
9.7.
Si desidera costruire un programma che “filtri un file di testo memorizzato
su disco e stampi di fianco a ogni parola la riga su cui si trova.” Richieste:
realizzare il solo main() facendo uso del filtro StreamTokenizer.
appunti java
pag. 130
9.8.
Si desidera costruire un programma che “Dopo aver acquisito una parola
da ricercare, filtri un file di testo memorizzato su disco e stampi quante volte
ricorre la parola nel testo.” Richieste: realizzare il solo main() facendo uso
del filtro StreamTokenizer.
9.9.
Si desidera costruire un programma che “Dopo aver acquisito una parola
da ricercare, filtri un file di testo memorizzato su disco e stampi su quali
righe si trova quella parola.” Richieste: realizzare il solo main() facendo uso
del filtro StreamTokenizer.
9.10.
Si desidera costruire un programma che “Filtri un file di testo memorizzato
su disco che contiene esclusivamente numeri separati da spazi e stampi la
somma algebrica dei numeri trovati.” Richieste: realizzare il solo main()
facendo uso del filtro StreamTokenizer.
Problemi la cui soluzione richiede la progettazione di una a più classi senza
Frame o GUI
9.11. Si desidera costruire un programma che “usi un flusso qualsiasi di caratteri
di input e mostri il testo completo sulla console in output."
Indicazioni:
il flusso da cui provengono i caratteri è un generico Reader (a seconda delle
necessità il dispositivo potrà essere uno StringBuffer, una Strimg, un Array o un
File di caratteri, la Tastiera). Si tratta quindi di progettare una Classe. (Chi fa che
cosa?)
la Classe, chiamiamola, Leggi_mostra avrà un costruttore generico di tipo Reader e
disprrà di almeno un metodo leggi() che acquisisce dallo stream i caratteri per
costruire una Stringa (che deve essere memorizzata negli attributi), e un secondo
metodo che mostra() la stringa su console.
Leggi_mostra
- Reader flusso;
- String testo;
+Leggi_mostra(Reader in)
+String leggi()
+void mostra()
Richieste:
implementare la classe e un main di prova per verificare i due metodi al variare
dello stream reader associato.
9.12. Si desidera costruire un programma che “analizzi un flusso qualsiasi di
caratteri e consenta di sapere se il testo contiene token numerici e/o token di
tipo word"
Indicazioni:
I casi d'uso potrebbero essere i seguenti:
costruttore del Filtro (quali parametri ?).
realizzare una funzionalità (metodo ?) che risponda alla domanda
quanti_num();
appunti java
pag. 131
realizzare una funzionalità (metodo ?) che risponda alla domanda
quante_par()
opzionalmente:
Filtro()
dammi_num()
dammi_par()
quanti_num()
quante_par()
Il flusso sarà un generico Reader (a seconda delle necessità il dispositivo potrà
essere uno StringBuffer, una Strimg, un Array o un File di caratteri, la Tastiera).
L'analisi del flusso verrà eseguita utilizzando uno StreamTockenizer. Si tratta
quindi di progettare una Classe Filtro. (Chi fa che cosa?)
Rischieste:
o
Disegnare (diagramma di classe) la classe Filtro;
o
Codificare la Classe e un main() di prova che verifichi le funzionalità
realizzate;
Problemi la cui soluzione richiede la progettazione di una a più classi con
l'uso di una Frame ed eventi GUI.
9.13. Si desidera costruire un programma che “analizzi (filtri) un testo contenuto
in un campo di una frame e mostri le elaborazioni in un secondo campo della
frame alla pressione dell'opportuno pulsante."
Richieste a:
o si immagini che il testo nel primo campo sia immesso dall'utente e il
filtro esegua una banale ricopiatura del testo di input nel secondo campo
alla pressione di un button.
o progettare una classe Filtro che abbia un metodo mostra() che
restituisce il testo di input inalterato.
o progettare la GUI (Finestra) con i due campi e il button che attiva il
comando mostra();
o progettare un main() che attivi e verifichi il funzionamento.
Richieste b:
o si immagini che il testo nel primo campo sia, come nella richiesta (a),
immesso dall'utente e il filtro esegua una banale ricopiatura del testo di
input nel secondo campo alla pressione di un button.
o Usare la classe Leggi_mostra progettata nell'esercizio 9.11.
o progettare la GUI con i due campi e il button che attiva il comando
mostra();
o progettare un main() che attivi e verifichi il funzionamento.
Richiesta c:
o immagini che il testo nel primo campo sia, come nella richiesta (a),
immesso dall'utente e il filtro scriva nel secondo campo il messaggio "ci
sono/non ci sono parole o numeri". Il filtro deve accertare, alla pressione
di un button, se il primo campo ha parole o numeri.
o Usare la classe progettata nell'esercizio 9.12.
o progettare la GUI con i due campi e il button che attiva il comando;
appunti java
pag. 132
o progettare un main() che attivi e verifichi il funzionamento.
9.14. Come nell'esercizio 9.13 si desidera costruire un programma che “analizzi
(filtri) un testo contenuto in un file su disco e caricato in un campo di una
frame e mostri le elaborazioni in un secondo campo della frame alla
pressione dell'opportuno pulsante."
Richiesta:
o Si modifichi il codice degli esercizi 9.13, per ottenere quanto richiesto,
sfruttando ancora sia la classe Leggi_mostra che la classe Filtro.
o All'avvio del main() la finestra si dovrà aprire e mostrare un campo per
richiedere il nome del file e quindi dovrà mostrare nel primo campo
riempito il file nel secondo le elaborazioni. (le parole numerate (se ne
contiene), le cifre)