I tipi di memoria
Come usare i file in Java
Schema visto fino a questo momento:
Schema molto approssimativo.
Bastava per le cose viste finora:
Ogni computer ha due tipi di dispositivi di memorizzazione:
Piccolo/grande: quanti dati posso memorizzare
Non volatile/volatile: i dati restano/non restano quando si spegne.
Anticamente:
Memoria di lavoro=RAM
memoria di massa=dischi
La distinzione non è più cosí netta.
Usiamo ancora questo modello, anche se impreciso, perchè ci fa comodo.
Nella memoria di lavoro vengono memorizzate variabili e oggetti.
La lavagna con i rettangolini e rettangoloni era la memoria di lavoro.
Come usare la memoria di lavoro:
Per usare la memoria di massa, dobbiamo sapere come è organizzata.
I dati sono memorizzati in file
Esempi di file: i sorgenti Java, i file .class (il risultato della compilazione), il file autoexec.bat, ecc.
Nei primi sistemi: sul disco c'era un insieme di file.
Attualmente: un disco può contenere 10000 file
Difficile trovare il file (a meno che non si sappia esattamente il nome)
Suddivisione in cartelle
Ogni cartella è un contenitore con dentro dei file, oppure altre cartelle.
md nomecartella
Crea una nuova cartella
cd nomecartella
Entra nella cartella
dir
Visualizza l'elenco dei file nella cartella in cui ci si trova
type nomefile
Visualizza il contenuto del file
Classi che riguardano la lettura da file:
Tutte riguardano i file che vogliamo leggere
Ognuna permette delle operazioni diverse
Un oggetto indica nome e caratteristiche del file, più che il suo contenuto.
Alcuni metodi della classe:
delete e renameTo ritornano un booleano che dice se l'operazione è riuscita oppure no
Possiamo fare finta che il valore ritornato sia void
Questo programma vede se il file abcd.txt esiste oppure no
import java.io.*; class EsisteFile { public static void main(String args[]) throws IOException { File f; f=new File("abcd.txt"); if(f.exists()) System.out.println("Il file abcd.txt esiste"); else System.out.println("Il file abcd.txt non esiste"); } }
import java.io.*;
Dice che voglio usare classi che riguardano l'I/O
throws IOException
Serve perchè i metodi di gestione dell'I/O possono generare errori (eccezioni)
f=new File("abcd.txt");
Costruttore con argomento: oltre a creare l'oggetto, dice un valore da memorizzare
Il file sta su disco, ha un nome e un contenuto
L'oggetto File è un oggetto che sta in memoria, e ha solo il nome del file.
L'oggetto rappresenta il file
Non tutto: solo alcune informazioni (nome)
La new non crea il file, ma solo l'oggetto in memoria.
È come per tutti gli altri oggetti: new Persona non crea una nuova persona, ma solo una nuova rappresentazione di una persona.
Il file e l'oggetto File sono due cose indipendenti
Può esistere il file ma non l'oggetto, e può esistere l'oggetto ma non il file.
Dopo aver fatto la new, esiste un oggetto in memoria.
Non è detto che il file esista realmente
Invocare il metodo exists = chiedere se esiste un file con il nome usato per la creazione dell'oggetto.
Se non esiste, f.exists() ritorna false, ma non è un errore.
I metodi mettono in relazione l'oggetto in memoria con quello su disco.
Per esempio, exists verifica se sul disco c'è un file che corrisponde a quello in memoria.
delete cancella il file che corrisponde a quello in memoria, se c'è
Attenzione! delete cancella il file, ma non l'oggetto File! (che continua ad esistere).
Verificare se il file prova.txt esiste.
Se esiste, cambiare nome in altro.txt, e poi cancellare prova2.txt
Usare i metodi delete e renameTo
come se non avessero valore di ritorno
(come se fossero metodi void)
import java.io.*; class EsFile { public static void main(String args[]) throws IOException { File f; f=new File("prova.txt"); if(f.exists()) { File g; g=new File("altro.txt"); f.renameTo(g); File h; h=new File("prova2.txt"); h.delete(); } } }
Nota: quando faccio File g, non sto creando il file (su disco) ma solo l'oggetto (in memoria).
Il file esiste solo dopo che ho fatto f.renameTo(g), che cambia il nome del file
Dopo il cambio di nome, il file f non esiste più!
Però esiste ancora l'oggetto f!
Dato che l'oggetto esiste, si possono invocare ancora i suoi metodi, per esempio f.exists()
Però il file non c'è più, per cui f.exists() restituisce false (non esiste più il file con il nome prova.txt)
L'oggetto non rappresenta il file, ma solo alcuni dei suoi attributi (nome, ecc).
renameTo cambia il nome al file, non all'oggetto
Si usano due classi:
La prima classe serve per la lettura dei singoli caratteri, la seconda per la lettura di stringhe.
Per le stringhe ho la classe String
Per i singoli caratteri ho un tipo scalare char
char uncar; uncar='a';
Quindi, il carattere 'a' viene memorizzato
dentro la variabile
(per le stringhe, nella variabile c'è il
riferimento)
A ogni carattere corrisponde un numero.
Per esempio, il carattere 'a' ha numero 97, il carattere 'b' ha numero 98, il carattere ] ha numero 93, ecc.
Conversione da numeri a caratteri:
int x=97; char c; c=(char) x;
Gli oggetti della classe rappresentano file che possiamo leggere.
Metodo read che legge un singolo carattere; ritorna un intero che lo rappresenta.
FileReader fr; fr=new FileReader("prova.txt"); int x; char c; x=fr.read(); c=(char) x;
Il metodo read ritorna un intero, che poi viene convertito in carattere.
Ogni volta che si invoca il metodo, viene letto un nuovo carattere.
Leggere e stampare i primi dieci caratteri del file prova.txt
Se non riusciamo a capire come si risolve, proviamo una cosa più semplice.
Leggere i primi due caratteri
import java.io.*; class LeggiChar { public static void main(String args[]) throws IOException { FileReader fr; fr=new FileReader("prova.txt"); int x; char c; x=fr.read(); c=(char) x; System.out.println(c); x=fr.read(); c=(char) x; System.out.println(c); } }
Prima creo l'oggetto FileReader, poi leggo i due caratteri.
La lettura va ripetuta dieci volte.
Uso un ciclo
import java.io.*; class LeggiDieci { public static void main(String args[]) throws IOException { FileReader fr; fr=new FileReader("prova.txt"); int x; char c; int i; for(i=0; i<10; i++) { x=fr.read(); c=(char) x; System.out.println(c); } } }
I valori interi possono venire convertiti in caratteri.
Se il valore letto è -1, allora il file è finito.
Scrivere un programma che legge tutti i caratteri di un file.
import java.io.*; class Fine { public static void main(String args[]) throws IOException { FileReader fr; fr=new FileReader("prova.txt"); int x; char c; // ciclo di lettura } }
Creo l'oggetto
Poi devo leggere i caratteri.
Uso un ciclo
So che il file è finito solo quando read ritorna -1
Non so quanti caratteri devo leggere per arrivare alla fine.
È un ciclo indefinito.
Uso while
import java.io.*; class Fine { public static void main(String args[]) throws IOException { FileReader fr; fr=new FileReader("prova.txt"); int x; char c; while(x!=-1) { x=fr.read(); if(x!=-1) { c=(char) x; System.out.println(c); } } } }
Esco dal ciclo quando x diventa uguale a -1
Errore: variabile x non inizializzata.
import java.io.*; class Fine { public static void main(String args[]) throws IOException { FileReader fr; fr=new FileReader("prova.txt"); int x=0; char c; while(x!=-1) { x=fr.read(); if(x!=-1) { c=(char) x; System.out.println(c); } } } }
Devo mettere un valore che faccia eseguire almeno una volta il ciclo.
Faccio un ciclo infinito.
Se leggo -1, esco
int x; char c; while(true) { x=fr.read(); if(x==-1) break; c=(char) x; System.out.println(c); }
Leggo il primo elemento prima di entrare nel ciclo.
int x; char c; x=fr.read(); while(x!=-1) { c=(char) x; System.out.println(c); x=fr.read(); }
Sono come i cicli while, soltanto che il corpo del ciclo viene eseguito almeno una volta.
do istruzione; while (condizione);
Al posto della istruzione posso mettere un blocco
Equivale a:
istruzione; if(!condizione) esci istruzione if(!condizione) esci istruzione if(!condizione) esci ...
L'unica differenza con il while è che l'istruzione viene eseguita sempre almeno una volta.
Esercizio: fare il programma di sopra con do-while
Il ciclo while di partenza non funzionava:
int x; char c; while(x!=-1) { x=fr.read(); if(x!=-1) { c=(char) x; System.out.println(c); } }
Non funzionava perchè il corpo del ciclo andava eseguito almeno una volta.
int x; char c; do { x=fr.read(); if(x!=-1) { c=(char) x; System.out.println(c); } } while(x!=-1);
Il corpo è rimasto lo stesso.
do istruzione; while(condizione);
Equivale a:
istruzione; while(condizione) istruzione;
Non si poteva fare:
// codice errato int x; char c; do { x=fr.read(); c=(char) x; System.out.println(c); } while(x!=-1);
Il file poteva non contenere nemmeno un carattere (file vuoto).
In questo caso, veniva stampato un carattere, anche se il file non ne contiene nessuno.
Con FileReader posso leggere un solo carattere per volta.
La classe BufferedReader ha un metodo per leggere una linea per volta.
Ogni linea del file viene letta come stringa
Le stringhe possono poi venire convertite in interi, ecc.
Non si crea dando il nome del file, ma dando un oggetto FileReader
FileReader f; f=new FileReader("prova.txt"); BufferedReader b; b=new BufferedReader(f);
La classe BufferedReader ha il metodo readLine(), che ritorna la prossima linea letta da file.
Leggere e stampare le prime due linee del file.
import java.io.*; class PrimeDue { public static void main(String args[]) throws IOException { FileReader f; f=new FileReader("prova.txt"); BufferedReader b; b=new BufferedReader(f); String s; s=b.readLine(); System.out.println(s); s=b.readLine(); System.out.println(s); } }
Ogni volta che invoco il metodo, viene letta una nuova riga dal file.
Leggere e stampare tutte le linee del file
Quando il file è finito, readLine() ritorna il valore null
È come la lettura per caratteri, con null al posto di -1
Si può fare s==null
oppure s!=null,
anche se s è un oggetto.
È un ciclo indefinito.
Si esce quando s è null
import java.io.*; class Tutte { public static void main(String args[]) throws IOException { FileReader f; f=new FileReader("prova.txt"); BufferedReader b; b=new BufferedReader(f); String s; while(true) { s=b.readLine(); if(s==null) break; System.out.println(s); } } }
Si può fare anche con do-while ecc.
Prima va creato il FileReader e poi con questo si crea il BufferedReader
null è un valore speciale, (l'oggetto non esiste)
Lo approfondiamo in seguito.
Nota: non ci sono le virgolette su null
Si può fare s==null (mentre s==q non va usato)
Le stringhe si possono convertire in numeri:
x=Integer.parseInt(s); d=Double.parseDouble(s);
La stringa letta con readLine() è una linea di un file.
Se il file contiene un intero per linea, si può direttamente convertire la stringa.
Se una linea contiene due interi, oppure una stringa che non è un intero, la conversione dà un errore.
Dato un file che contiene un intero per linea, stampare tutti gli interi aumentati di due.
Si può scrivere in questo modo:
per ogni intero su file stampa l'intero piu' due
La stampa di un intero aumentato di due è facile
Dato che va fatto per tutti gli interi del file, devo fare la lettura di tutti gli interi.
Uso il solito ciclo di lettura di tutte le righe del file
Ogni volta che ho trovato una linea, la converto in intero, lo aumento di due e la stampo.
import java.io.*; class SommaDue { public static void main(String args[]) throws IOException { FileReader f; f=new FileReader("numeri.txt"); BufferedReader b; b=new BufferedReader(f); String s; int x; while(true) { s=b.readLine(); if(s==null) break; x=Integer.parseInt(s); System.out.println(x+2); } } }
È il solito ciclo di scansione.
L'unica differenza è che non stampo s, ma lo converto prima in intero e lo aumento di due.
Perchè devo convertire in intero?
Perchè la somma non è definita
sulle stringhe
(il simbolo + ha un altro significato
sulle stringhe)
Dato un file che contiene un intero per linea, stampare solo la somma.
Suggerimento?
Abbiamo visto come si leggono tutti i valori su un file.
Abbiamo visto come si sommano dei numeri interi che sono valori di una funzione.
Qui devo leggere e sommare i valori letti da file.
Uso il metodo dell'accumulatore (risultato parziale):
Una variabile memorizza la somma degli elementi letti fino a questo momento.
Inizialmente è zero.
Ogni volta che leggo un nuovo intero, lo sommo al contenuto della variabile.
La soluzione combina la somma dei valori di una funzione (metodo dell'accumulatore) e la lettura di interi da file.
import java.io.*; class SommaTutti { public static void main(String args[]) throws IOException { FileReader f; f=new FileReader("numeri.txt"); BufferedReader b; b=new BufferedReader(f); String s; int x; int somma=0; while(true) { s=b.readLine(); if(s==null) break; x=Integer.parseInt(s); somma=somma+x; } System.out.println(somma); } }
Trovare il minimo elemento di un file di interi.
Uso il metodo del risultato parziale (simile a quello dell'accumulatore).
Invece di una variabile con la somma degli elementi trovati fino a questo momento, ho l'elemento minimo fra quelli visti finora.
Ogni volta che leggo un elemento, se è minore del minimo, allora cambio la variabile:
per ogni intero x del file se e' minore del minimo minimo=x
In questo modo, minimo contiene sempre l'elemento più piccolo visto fino a questo momento.
Trasformo il discorso di sopra in codice:
import java.io.*; class MinimoProv { public static void main(String args[]) throws IOException { FileReader f; f=new FileReader("numeri.txt"); BufferedReader b; b=new BufferedReader(f); String s; int x; int minimo; while(true) { s=b.readLine(); if(s==null) break; x=Integer.parseInt(s); if(x<minimo) minimo=x; } System.out.println(minimo); } }
La variabile minimo potrebbe non essere stata inizializzata.
int minimo; while(true) { ... if(x<minimo) { minimo=x; } } System.out.println(minimo);
Se il file è vuoto, si esce subito dal ciclo e si stampa minimo, che non è inizializzato.
Se il file non è vuoto, si verifica la condizione x<minimo quando minimo non è stato inizializzato.
Quindi, ha ragione il compilatore a dire che la variabile non è stata inizializzata.
Non è il massimo intero possibile.
Il risultato parziale iniziale va trovato in base alle specifiche:
il minimo elemento di un insieme è l'elemento dell'insieme per cui non ne esiste uno minore
Il risultato parziale iniziale è il minimo dell'insieme vuoto.
Ma l'insieme vuoto non ha un elemento minimo, dato che il minimo è un elemento dell'insieme.
Quindi, il minimo dell'insieme vuoto non è definito.
Caso base: insieme di un solo elemento.
Se l'insieme ha un solo elemento, questo è il minimo.
Inizializzazione: il minimo è il primo elemento dell'insieme.
Questo risultato parziale è corretto:
visto che ho considerato solo il primo elemento,
il risultato parziale è effettivamente
il minimo fra gli elementi visti finora.
Leggo il primo intero, e lo metto in minimo
Se il file è vuoto, il minimo non è definito.
Se il file contiene almeno un elemento, allora il minimo iniziale (dopo aver considerato il primo) è corretto.
import java.io.*; class Minimo { public static void main(String args[]) throws IOException { FileReader f; f=new FileReader("numeri.txt"); BufferedReader b; b=new BufferedReader(f); String s; int x; s=b.readLine(); if(s==null) return; x=Integer.parseInt(s); int minimo=x; while(true) { s=b.readLine(); if(s==null) break; x=Integer.parseInt(s); if(x<minimo) minimo=x; } System.out.println(minimo); } }
L'istruzione return termina l'esecuzione di main
Le tre classi rappresentano sempre i file, però in modo diverso:
Le classi da usare sono:
Quando si crea un oggetto, va detto il nome del file:
FileWriter w; w=new FileWriter("prova.txt");
Il metodo write della classe permette di scrivere un singolo carattere.
Alla fine, va invocato il metodo flush
Questo programma scrive su file questi due caratteri
import java.io.*; class ScriviDue { public static void main(String args[]) throws IOException { FileWriter w; w=new FileWriter("prova.txt"); w.write('a'); w.write('b'); w.flush(); } }
Perchè flush?
Il metodo charAt() della classe String restituisce il carattere in una certa posizione.
Esempio:
String s="abcdefh"; char c; c=s.charAt(3);
Restituisce il carattere in posizione 3 (ossia il quarto).
Scrivere la stringa "abcdefghi" sul file prova.txt
I dati necessari li avete:
Per ogni carattere della stringa, lo scrivo su file
Il ciclo deve andare da 0 a s.length()-1
import java.io.*; class ScriviStringa { public static void main(String args[]) throws IOException { FileWriter w; w=new FileWriter("prova.txt"); String s="abcdefghi"; int i; char c; for(i=0; i<s.length(); i++) { c=s.charAt(i); w.write(c); } w.flush(); } }
Permette di scrivere stringhe su file
Il metodo write prende come argomento una stringa.
Usare il carattere \n per andare a capo
import java.io.*; class ScriviUna { public static void main(String args[]) throws IOException { FileWriter w; w=new FileWriter("prova.txt"); BufferedWriter b; b=new BufferedWriter (w); b.write("abcd\nefghi"); b.write("123"); b.flush(); } }
Contenuto del file:
abcd efghi123 |
Il ritorno a capo appare solo dove c'è \n
Non c'è dove finisce la prima stringa stampata.
Stampare su file i valori della funzione f(x)=x2-10x+4 per x intero da -10 a 10
Usare il metodo String Integer.toString(int) per convertire un intero in stringa (parametro intero e valore di ritorno stringa)
Faccio un ciclo
Calcolo i valori di f, li converto in stringa e li stampo su file.
Dopo ogni valore, scrivo il ritorno a capo (stringa "\n", ossia la stringa che contiene un solo carattere '\n')
import java.io.*; class ScriviFunzione { public static void main(String args[]) throws IOException { FileWriter w; w=new FileWriter("prova.txt"); BufferedWriter b; b=new BufferedWriter (w); int x, f; String s; for(x=-10; x<=10; x++) { f=x*x-10*x+4; s=Integer.toString(f); b.write(s); b.write("\n"); } b.flush(); } }