Appunti java
pag. 133
10. File e loro attributi
In generale, in qualsiasi sistema operativo, il file system consente di operare su
archivi di dati (data files) che possono essere organizzati e memorizzati su
supporto magnetico in due modi fondamentali:
1. Come sequenze di record di lunghezza variabile individuate da
particolari marcatori di fine record;
2. Come sequenze di record di lunghezza fissa senza marcatori;
Il file di testo è il “più duttile” nel senso che può assumere entrambe le vesti
indicate in precedenza.
Assume la prima configurazione, ovvero è un file di record (il record è una linea di
testo) di lunghezza variabile terminata da uno o più marcatori di fine linea End Of
Line (EOL).
La seconda configurazione è sempre “disponibile” e consiste nel pensare il file di
testo come file con record di lunghezza fissa (il record è un carattere pari a uno o
due byte). In questo caso si deve pensare al marcatore di fine linea come a un
particolare carattere che non ha “immagine” ma che “stampato” genera un ritorno
carrello spostando il cursore all’inizio della linea successiva del dispositivo di
stampa (video o stampante).
In informatica quando si parla di file di testo ci si riferisce alla prima
interpretazione avvero a quella di file con record pari a una linea di testo di
lunghezza variabile. Il file deve, di conseguenza, essere generato o letto in
sequenza dal primo all’ultimo record e può subire aggiornamenti solo nella forma
di “addizione in coda” (append) di nuove righe.
Si può concludere, quindi, che un file di record di lunghezza variabile consente la
sola metodologia di ACCESSO SEQUENZIALE.
Il secondo tipo di file che opera su record di dati di lunghezza fissa predefinita,
oltre all’ACCESSO SEQUENZIALE consente un ACCESSO INDICIZZATO che
permette “salti” al record di posizione relativa scelta dall’utente. La lunghezza
fissa di ogni record consente anche la SOVRASCRITTURA di un record intermedio
oltre alla “addizione in coda” di nuovi record specifica dei file sequenziali.
Un file è quindi una struttura dati “permanente” (mentre il vettore è “volatile”
perché memorizzato in memoria di lavoro) costituita da una sequenza di RECORD
virtualmente illimitata come mostrato in figura:
ATT
INIZ
......
EOF
Ogni file viene “scandito” (letto, sfogliato) leggendo un record e portandolo in
memoria di lavoro. A tale fine deve quindi essere dotato di un puntatore INIZiale
che indica dove si trova la sua TESTA e di un puntatore ATTuale che indica la
posizione della successiva lettura. Infine deve possedere un particolare
MARCATORE che indica la sua fine EOF=EndOfFile. Se il puntatore ATT è
posizionato sul marcatore EOF la lettura è terminata e non è più possibile
avanzare.
Appunti java
pag. 134
Le due tipologie di file precedentemente descritte hanno, nei linguaggi imperativi
tradizionali (es. Pascal), definizioni e metodologie di accesso distinte che fanno
riferimento alla loro struttura. La struttura fisica di memorizzazione sui
dispositivi per i file di tipo TEXT può essere diversa a seconda del sistema
operativo e/o del linguaggio che li genera. In particolare il marcatore di fine linea
può essere costituito da due marcatori di fine riga (in Pascal/Windows ad
esempio CARRIAGE RETURN CR=13, LINE FEED LF=10) o da un solo marcatore
(New Line ‘\n’ in c/UNIX). La struttura fisica di memorizzazione dei FILE di
RECORD è di norma costituita da registrazioni binarie di lunghezza fissa senza
marcatori con l’esclusione dell’EOF.
10.1. File in linguaggio Java
Il linguaggio Java, con l’uso degli stream e dei filtri, visti nel capitolo precedente,
tenta di superare questa classica suddivisione tra le due tipologie di file. Si è visto
che un flusso FileReader o InputStreamReader consente di accedere in input ai
singoli caratteri con il metodo read(). Viceversa introducendo il filtro
BufferedReader si può accedere ad una intera linea con il metodo readLine().
Per non dipendere dal sistema operativo o dal linguaggio che ha creato il file di
testo java interpreta come EndOfLine corretto sia la presenza di uno dei
marcatori CR=13 (‘\r’) oppure LF=10 (‘\n’) oppure la presenza contemporanea di
entrambi i caratteri ‘\r’+’\n’.
I file con record di lunghezza fissa sono invece memorizzati come byte binari
dipendenti dalla dimensione del tipo di dato elementare che si intende registrare.
Questi non hanno in Java un particolare trattamento separato dai file di Testo
infatti la classe con nome “improprio” Random Access File consente di trattare
entrambi i tipi di file.
10.2. I metodi che operano sui file
In sostanza i file di tipo TEXT possono subire due trattamenti, per linee o per
singolo carattere. Nel primo caso i record sono righe nel secondo li si tratta come
file con record di lunghezza fissa di uno o due byte.
La diversità di struttura fisica si traduce solo nella disponibilità di due modalità
di accesso scelte dall’utente.
Se un file di testo lo si “vede” come file di linee vi si accede solo con operazioni
sequenziali di lettura o solo con l’addizione di record in coda in scrittura, se lo si
vede invece come file di caratteri sono consentite le operazioni di SALTO al record
(carattere) di posizione desiderata, sia per la scrittura (distruttiva) che per la
lettura di un record. I record di un file (con record costante) hanno quindi una
posizione (INDICE) relativa ben definita che parte dal 1° record, che ha posizione
relativa o indice 0, fino all’n-esimo con posizione N-1.
Struttura fisico-logica di un FILE di RECORD di lunghezza fissa:
0
1
...........
N-1
Indice di Posizione relativa
I metodi che caratterizzano una classe che opera sui file si possono quindi
raggruppare logicamente in quattro categorie:
Appunti java
♦
♦
♦
♦
pag. 135
Costruttori
Metodi di accesso agli attributi del file
Metodi di lettura
Metodi di scrittura.
10.3.
La classe RandomAccessFile di Java
La tabella seguente elenca i metodi della classe RandomAccessFile.
RandomAccessFile
Costruttori
RandomAccessFile(String, String)
NOTE
La prima stringa è un nome di file fisico, la
seconda indica il modo con cui si accede. Il
modo può essere di lettura modo read “r” o
in lettura e scrittura modo “rw”. In modalità
“rw” se non esiste lo crea, se esiste si
predispone per le operazioni di lettura o
scrittura ponedo il puntatore all’inizio. Invia
una IOException se in modalità “r” non
esiste il file o si tratta di una directory.
Metodi
long length()
void setLength(long)
long getFilePointer()
void seek(long)
Metodi di lettura
Metodi di Scrittura
void close()
int
readInt()
long readLong()
double readDouble()
float
byte
boolean
char
String readLine()
Attenzione: in scrittura è a carico del
programmatore aggiungere un carattere di
fine linea ‘\r’ o ‘\n’ o entrambi.
void
writeInt(int)
void
writeLong(long)
void
writeDouble(double)
void
writeFloat(float)
void
writeByte(int)
void
writeBoolean(boolean)
void
writeChar(int)
void
writeBytes(String)
void
writeChars(String)
Restituisce il numero di byte del file.
Setta la lunghezza del file. Se il file e più
corto lo amplia, se più lungo lo tronca. In
caso di riscrittura su un file esistente è
necessario utilizzare questo metodo
al
termine della scrittura per inserire l’eof.
Nella forma
setLength( getFilePointer()).
Restituisce la posizione in byte del puntatore
attuale
Porta il puntatore sul byte di posizione
indicata
Chiude il file e libera la memoria heap
Legge un dato e trasla il puntatore di una
quantità pari al numero di byte binari
occupati dal tipo di dato.
La lettura produce un EOFException quando
incontra il termine del File
Legge tutti i caratteri fino al marcatore di
fine linea ‘\r’ o ‘\n’. Se il puntatore è a EOF
la stringa acquisita è null. In ogni caso si
produce un EOFException
Scrive il dato indicato e trasla il puntatore.
Scrive tutti i byte di una stringa in formato
dipendente dalla macchina.
Scrive tutti i byte di una stringa in formato
Unicode.
Appunti java
pag. 136
♦ Lettura di un file di testo
Siccome anche un file sequenziale di testo può essere trattato con i metodi della
classe Random Access File si affronterà per primo un esercizio di lettura e stampa
di un file di testo.
esempio 1. “Assegnato un file di testo memorizzato su disco il cui nome fisico sia
<C:\mio.txt> leggerlo e stamparlo sul monitor.” Richiesta:
Ricordando che un file di testo è organizzato in linee che terminano con un
marcatore di fine linea, utilizzare i metodi opportuni di RAF per acquisire e
stampare una linea completa per volta fino a fine file.
Codifica:
import java.io.*;
public class cap10_es_01 {
public static void main(String args[])throws IOException
{
RandomAccessFile in=new RandomAccessFile("c:/mio.txt","r");
System.out.println("\nfile di "+in.length()+" byte contiene:");
String dat;
while ((dat=in.readLine())!=null)
System.out.println(dat);
System.out.println("\nFine esecuzione.");
}
}
// (1)
// (2)
// (3)
Esecuzione:
Il file stampato è costituito da cinque linee come mostra l’output della finestra.
Commento al codice:
La nota (1) mostra il costruttore del file random che assume il nome logico in. La
nota (2) evidenzia l’utilizzo del metodo length() per stampare la lunghezza in byte
del file, la (3) il metodo che acquisisce una linea del file e testa il raggiungimento
della fine del file. Se si contano i caratteri stampati si nota che le 5 righe
contengono 70 caratteri e di conseguenza gli 80 byte indicati nell’autput sono
inputabili alla coppia di marcatori di EOL \r \n presenti su ogni riga.
♦
Creazione di un file di testo
esempio 2. “realizzare un programma che generi in un ciclo 100 numeri da 1000 a
1099 e dopo averli trasformati in stringa li scriva su una singola riga del file.”
Richieste:
Oltre a generare il file richiesto il main() deve anche invocare un metodo di
stampa() separato per verificare sul monitor se il programma ha svolto quanto
richiesto.
Appunti java
pag. 137
Codifica:
import java.io.*;
public class cap10_es_02 {
public static void Stampa(String n_fil)throws IOException {
RandomAccessFile in=new RandomAccessFile(n_fil,"r"); // (4)
System.out.print("\nil file lungo "+in.length()+" byte"); // (5)
System.out.println(" contiene:\n");
String dat;
while ((dat=in.readLine())!=null)
// (6)
System.out.println(dat);
}
public static void main(String args[])throws IOException {
String n_fil="c:/nuovo.dat";
RandomAccessFile out=new RandomAccessFile(n_fil,"rw"); // (1)
for (int i=0; i<100; i++)
out.writeBytes(""+(i+1000)+'\n');
// (2)
out.setLength(out.getFilePointer());
// (3)
out.close();
// (4)
Stampa(n_fil); // stampa del file generato
System.out.println("\nfine esecuzione.\n");
}
}
Esecuzione:
La finestra di output mostra che il file contiene su ogni riga i numeri desiderati
trasformati in testo e che la lunghezza totale del file è 500 byte. Infatti una riga
contiene 5 caratteri; quattro numerici più il marcatore di fine linea ‘\r’.
Commento al codice:
La nota (1) indica il costruttore del file in modalità scrittura “rw”. La nota (2)
indica il ciclo di costruzione delle righe che vengono scritte nel file. In particolare
il metodo writeBytes(String) ha come parametro una String e quindi il doppio
apice “”+(i+1000)+’\r’ che precede il numero generato nel ciclo è indispensabile
per trasformare immediatamente il numero in un stringa. Il carattere ‘\r’ è il
marcatore fine linea inserito nella stringa. In alternativa si poteva scrive un ‘\n’
oppure una sequenza ‘\r’+’\n’. La nota (3) indica la modalità con cui si deve
inserire il Fine File EOF usando il metodo setLength( long) dove il parametro
numerico è la lunghezza in byte del file che si intende marcare con EOF. Il close()
(4) successivo non è indispensabile ha la sola funzione di liberare lo heap dagli
oggetti File costruiti.
Le note (5) e (6) indicano la necessità di usare nuovamente il costruttore in
modalità “r” per leggere il file se prima è stato chiuso con close().
Appunti java
pag. 138
♦ Appendere righe a un file di testo
esempio 3. “realizzare un programma che aggiunga al precedente file di testo altri
10 numeri da 1099 a 1108 e li scriva in coda al file esistente.” Richieste:
Oltre ad appendere i dati al file il main() deve anche invocare un metodo di
stampa() separato per verificare sul monitor se il programma ha svolto quanto
richiesto.
Codifica:
import java.io.*;
public class cap10_es_03 {
public static void Stampa(String n_fil)throws IOException {
RandomAccessFile in=new RandomAccessFile(n_fil,"r");
System.out.print("\nil file lungo "+in.length()+" byte");
System.out.println(" contiene:\n");
String dat;
while ((dat=in.readLine())!=null)
System.out.println(dat);
}
public static void main(String args[])throws IOException {
String n_fil="c:/nuovo.dat";
RandomAccessFile out=new RandomAccessFile(n_fil,"rw");
Out.seek(out.length());
// posizionamento in coda del puntatore
for (int i=1100; i<1110; i++)
out.writeBytes(""+i+'\n');
out.setLength(out.getFilePointer());
out.close();
Stampa(n_fil); // stampa del file generato
System.out.println("\nfine esecuzione.\n");
}
}
Esecuzione:
l’output dovrebbe mostrare anche le nuove righe appese al procedente file.
Commenti:
La nota su out.seek(out.length()); mostra come posizionare il puntatore attuale a
fine file per eseguire l’append dei nuovi dati.
♦ Generare un file di dati
Per rendere evidente la diversità tra un file ti testo e uno di dati proponiamo il
seguente:
esempio 4. “realizzare un programma che generi in un ciclo 5 numeri da 10000 a
10004 e li scriva su un file di dati.” Richieste:
Oltre a generare il file richiesto il main() deve anche stamparlo per verificare se il
programma ha svolto quanto richiesto.
Appunti java
pag. 139
Codifica:
import java.io.*;
public class cap10_es_04 {
public static void main(String args[])throws IOException {
RandomAccessFile out=new RandomAccessFile("nuovo.dat","rw");
for (int i=0; i<5; i++)
out.writeInt(i+10000);
// (1)
out.setLength(out.getFilePointer());
/* verifica del file costruito */
System.out.print("\nil file è di :"+out.length()+" byte");
System.out.println("e contiene:\n");
out.seek(0); int dat=0;
// (2)
try { while (true) {
// (3)
dat=out.readInt();
// (4)
System.out.print(dat+" ");
}
} catch (EOFException e) {System.out.println("\nFINE FILE\n"); }
System.out.println("Esecuzione terminata.");
}
}
Esecuzione:
La finestra di output mostra che il file contiene i cinque numeri richiesti ma a
differenza del precedente programma non sono stampati su righe diverse. La
lunghezza totale del file è 20 byte, infatti un record e costituito da un numero che
pur essendo di 5 cifre non è memorizzato in modalità carattere ma in modalità
binaria e quindi occupa lo spazio di un int che in Java è pari a 4 byte.
Evidentemente non è più presente il marcatore di fine linea ‘\r’.
Commento al codice:
Nella prima parte nulla è cambiato rispetto all’esempio precedente solo la nota (1)
indica il nuovo metodo usato per scrivere un record intero writeInt().
Rispetto al precedente esempio non si è operata la chiusura del file con il metodo
close(), per evidenziare che in questo caso non è più necessario riusare in
costruttore ma è sufficiente posizionare il puntatore all’inizio del file con il metodo
seek(0) della nota (2).
Le righe con note (3) e (4) e successive mostrano il metodo che si può usare per
leggere un file di record. Il blocco try{ } catch (..) { } indica che il ciclo
apparentemente infinito while (true) viene interrotto quando si raggiunge il fine
file e il metodo readInt() genera un’eccezione che rimanda all’esecuzione del
blocco cath. Si nota infatti che nella finestra di output al termine del file è
stampato in messaggio di <FINE FILE> generato dall’eccezione EOFException.
Appunti java
pag. 140
♦ Generare un file di dati con più campi
Per rendere evidente come si opera su un file di dati formato da diversi campi si
propone il seguente:
esempio 5. “realizzare un programma che legga da tastiera una sequenza di nomi
di città seguite dal numero intero che rappresenta la popolazione e scriva in un
opportuno file di dati.” Richieste:
Oltre a generare il file richiesto il main() deve anche invocare una procedura
stampa() per verificare se il programma ha svolto quanto richiesto.
Il record da memorizzare è costituito da una Stringa e da un numero intero e si
decide di dedicare un massimo di 20 caratteri alla descrizione della città e in
intero per la popolazione (in java int è di 4 byte, più che sufficiente a contenere la
popolazione).
Siccome si devono acquisire i dati da tastiera, si può organizzare il programma
filtrando i dati con uno StreamTokenizer per separare con facilità le stringhe dai
numeri e quindi comporre il record di 24 byte da scrivere nel file di output. Lo
schema dei flussi e filtri potrebbe essere così rappresentato:
Flusso di Record
Programma.
RAF
(cons. di Token)
Flusso di Char Flusso di Token
ISR
ST
File
rappresentazione dei flussi del programma di I/O richiesto dal problema.
File
Flusso di Record
RAF
Prog. Stampa.
(cons. di Record)
I flussi del metodo stampa().
Codifica:
import java.io.*;
public class cap10_es_05 {
public static void stampa(String sfil) throws IOException{
RandomAccessFile out=new RandomAccessFile(sfil,"r"); // (20)
System.out.print("\nil file contiene:\n");
out.seek(0);
// (21)
String cit;int dat=0; byte[] b=new byte[20];
// (22)
try { while (true) {
out.read(b,0,20);
// (23)
cit=new String(b,0,20);
// (24)
dat=out.readInt();
// (25)
System.out.println(cit+" "+dat);
}
} catch (EOFException e) {System.out.println("\nFINE FILE\n"); }
}
Appunti java
public static void main(String args[])throws IOException {
String sfil_out="Citta.dat";
InputStreamReader in=new InputStreamReader(System.in);
StreamTokenizer st=new StreamTokenizer(in);
RandomAccessFile out=new RandomAccessFile(sfil_out,"rw");
out.seek(0);
// (4)
String s="";int pop=0;
st.nextToken();
while ((st.ttype!=st.TT_EOF)) {
if(st.ttype==st.TT_WORD){
s=st.sval;
// (5)
s=s+"
";
// (6)
s=s.substring(0,20);
// (7)
out.writeBytes(s);
// (8)
}
else if (st.ttype==st.TT_NUMBER) {
pop=(int)st.nval;
// (9)
out.writeInt(pop);
// (10)
}
st.nextToken();
}
out.setLength(out.getFilePointer());
// (11)
out.close();
stampa(sfil_out);
// (12)
System.out.println("Esecuzione terminata.");
}
}
pag. 141
// (1)
// (2)
// (3)
Commento al codice:
Le note da (1) a (3) mostrano i costruttori dei flussi indicati nello schema grafico.
La (4) il posizionamento in testa al file (non indispensabile perché è appena stato
aperto). Il ciclo separa i token e viene interrotto da un EOF immesso da tastiera
(si deve premere Ctrl-z su una riga vuota). Le note da (5) a (8) mostrano come si
acquisisce la stringa e la si satura di spazi bianchi dimensionandola a 20
caratteri e quindi la si scrive nel file di output. Le note (9) e (10) le analoghe
operazioni per scrivere il campo popolazione nel file. Le (11) e (12) sono
l’operazione di chiusura del file e quella di invocazione della stampa() di verifica.
Le note da (20) a (25) mostrano come si acquisisce un record e lo si mostra.
♦
Sovrascrivere in un file di dati
esempio 6. “realizzare un programma che legga da tastiera il nome di una città,
presente nel file del precedente esempio 5, la faccia seguire dal numero intero che
rappresenta
la
nuova
popolazione,
diversa
da
quella
memorizzata
precedentemente, quindi sovrascriva il record da aggiornare nel file di dati.”
Richieste:
Il main() deve anche invocare i metodi: leggi_cit() che acquisisce la stringa corretta
lunga 20 caratteri, leggi_int() per acquisire la popolazione, ricerca() che restituisce
la posizione del record da aggiornare, quindi il main sovrascrive il record stesso.
Infine stampa() verificherà se il programma ha svolto quanto richiesto.
Scomposizione:
Appunti java
pag. 142
Leggi int()
main()
Leggi cit()
ricerca()
stampa()
Codifica:
import java.io.*;
public class cap10_es_06 {
static void stampa(String sfil) throws IOException{
RandomAccessFile out=new RandomAccessFile(sfil,"r"); // (20)
System.out.print("\nil file contiene:\n");
out.seek(0);
// (21)
String cit;int dat=0; byte[] b=new byte[20];
// (22)
try { while (true) {
out.read(b,0,20);
// (23)
cit=new String(b,0,20);
// (24)
dat=out.readInt();
// (25)
System.out.println(cit+" "+dat);
}
} catch (EOFException e) {System.out.println("\nFINE FILE\n"); }
}
static String Leggi_cit() {return "Bologna
static int Leggi_int() {return 1111;
";}//lettura simulata
}
// lettura simulata
static long Ricerca(String cit, RandomAccessFile io) throws IOException{
String cit_f; byte[] b=new byte[20];
io.seek(0);
io.read(b,0,20);
// lettura 1° record
cit_f=new String(b,0,20);
// lettura 1° record
io.seek(24);
// punt sul 2° record
while (!cit.equals(cit_f)) {
io.read(b,0,20);
// lettura record succ
cit_f=new String(b,0,20);
io.seek(io.getFilePointer()+4);
// punt sul record succ
}
return (io.getFilePointer()-24);
}
public static void main(String args[])throws IOException {
String sfil="Citta.dat";
RandomAccessFile io=new RandomAccessFile(sfil,"rw");
String cit=Leggi_cit();
int pop=Leggi_int();
long pos=Ricerca(cit, io);
io.seek(pos);
// (1)
io.writeBytes(cit);
// (2)
io.writeInt(pop);
// (3)
io.close();
stampa(sfil);
System.out.println("Esecuzione terminata.");
}
}
Commento al codice:
Le note da (1) a (3) mostrano le operazione di sovrascrittura del record trovato.