I file

I tipi di memoria

Come usare i file in Java


Il calcolatore

Schema visto fino a questo momento:

Schema molto approssimativo.

Bastava per le cose viste finora:

CPU
parte che elabora
memoria
contiene variabili, oggetti, ecc.
In/Out
usiamo i metodi println e showInputDialog

Due tipi di memoria

Ogni computer ha due tipi di dispositivi di memorizzazione:

RAM
veloce, piccola, volatile
dischi
lenti, grandi, non volatili

Piccolo/grande: quanti dati posso memorizzare

Non volatile/volatile: i dati restano/non restano quando si spegne.


Memoria di lavoro/memoria di massa

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.


Gestione memoria di lavoro

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:


Gestione della memoria di massa

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.


Organizzazione gerarchica

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.


Cartelle: i comandi del prompt

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


Gestione dei file in Java

Classi che riguardano la lettura da file:

Tutte riguardano i file che vogliamo leggere

Ognuna permette delle operazioni diverse


La classe File

Un oggetto indica nome e caratteristiche del file, più che il suo contenuto.

Alcuni metodi della classe:

boolean exists()
vede se il file esiste
boolean delete()
cancella il file
boolean renameTo(File)
cambia il nome

delete e renameTo ritornano un booleano che dice se l'operazione è riuscita oppure no

Possiamo fare finta che il valore ritornato sia void


Esempio di programma

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");
  }
}

Osservazioni sul programma

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 e l'oggetto File

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)


Ma new File crea il file?

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.


Ma esiste il file?

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 di File

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


Esercizio

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)


Soluzione

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


Lettura dei file

Si usano due classi:

La prima classe serve per la lettura dei singoli caratteri, la seconda per la lettura di stringhe.


I singoli caratteri

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)


Numeri correlati ai caratteri

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;

La classe FileReader

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.


Esercizio

Leggere e stampare i primi dieci caratteri del file prova.txt


Soluzione semplificata

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.


Soluzione complessiva

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);
    }
  }
}

Leggere fino alla fine del file

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.


Creazione oggetto FileReader

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


Ciclo definito o indefinito?

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


Prima versione

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.


Versione corretta

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.


Soluzione alternativa con break

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);
    }

Soluzione con lettura primo elemento

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();
    }

Cicli do-while

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


Soluzione

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.


Trasformazione while -> do-while

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.


La classe BufferedReader

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.


Creazione di un BufferedReader e lettura

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.


Esercizio

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.


Esercizio: leggere tutto il 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.


Soluzione

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


Note sulla soluzione

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)


Conversione in intero

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.


Esercizio

Dato un file che contiene un intero per linea, stampare tutti gli interi aumentati di due.


Soluzione

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.


Soluzione

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.


Conversione stringa -> intero

Perchè devo convertire in intero?

Perchè la somma non è definita sulle stringhe
(il simbolo + ha un altro significato sulle stringhe)


Esercizio

Dato un file che contiene un intero per linea, stampare solo la somma.

Suggerimento?


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.


Soluzione

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.


Soluzione: codice

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);
  }
}

Esercizio

Trovare il minimo elemento di un file di interi.


Soluzione

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.


Soluzione provvisoria

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);
  }
}

Non compila!

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.


Quale deve essere il valore iniziale?

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.


E allora come faccio?

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.


Programma modificato

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


Differenza fra la classi

Le tre classi rappresentano sempre i file, però in modo diverso:

File
rappresenta le proprietà "globali" dei file, come il nome, la dimensione, ecc. I metodi sono operazioni su tutto il file (cancellare, cambiare nome ecc.)
FileReader e BufferedRreader
rappresentano il "contenuto" dei file, ossia i dati memorizzato. La differenza fra oggetti dei due tipi è nel modo in cui accedono al contenuto (per singolo carattere o per linea)

Se il file non esiste?

File
non è un errore: un oggetto di questo tipo rappresenta un file che può oppure può non esistere
FileReader e BufferedRreader
questi oggetti rappresentano file che voglio leggere; quindi, devono esistere: se non ci sono, viene generato un errore in fase di creazione (quando viene fatta la new)

Scrivere su file

Le classi da usare sono:

FileWriter
scrive singoli caratteri
BufferedWriter
scrive intere stringhe

La classe FileWriter

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


Esempio

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?


Come convertire stringhe in caratteri

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


Esercizio: scrivere una stringa su file

Scrivere la stringa "abcdefghi" sul file prova.txt

I dati necessari li avete:


Soluzione

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();
  }
}

La classe BufferedWriter

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.


Esercizio

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)


Soluzione

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();
  }
}