mercoledì 20 ottobre 2010

API - File

In Java abbiamo due tipi di approccio ai file: considerarli come entità (quindi File e Directory) o considerarli come contenuto (quindi Writer e Reader).

La gestione dei file come entità avviene esclusivamente tramite l'oggetto File; i suoi costruttori permettono di riferirci a percorsi fisici (esistenti o no) e i suoi metodi permettono di leggere directory, creare file vuoti o directory, cancellare file o directory (vuote), rinominare file o directory:

    File f = new File("folder"); //punto ad un file sul file system
    if (!f.exists())
        f.mkdir(); //creo la directory

    File innerFile = new File(f, "file.txt");
    innerFile.createNewFile();

    f.delete(); //non funziona, ritorna false
    if (f.isDirectory() && f.listFiles().length > 0)  innerFile.renameTo(new File("." + File.separatorChar + "test.txt"));

        f.delete(); //ora funziona

in questo esempio vediamo come si crea una directory ( mkdir() ), un file vuoto ( createNewFile() ), come si cancella un file o una directory vuota ( delete() ), e come si rinominano (o spostano) i file e le directory ( renameTo(File f) ), come si controlla se un file o una directory esistono già ( exists() ), se il File è un file o una directory (isFile() e isDirectory() ) e infine come ottenere tutti i file dentro una directory ( listFiles() o list() ).
Questi indicati sono tutti i metodi che servono per l'esame, e di solito nella vita reale.

Le classi relative alla scrittura e lettura del contenuto dei file si dividono in Writer/OutputStream e Reader/InputStream; la differenza tra Writer e OutputStream è che il primo lavora su caratteri mentre il secondo lavora su byte.
Importante ricordare che System.in è un InputStream, e System.out è un PrintWriter;
Per la certificazione non serve conoscere gli Stream, ma sono necessari se si vuole usare la console e non si vuole usare la classe Console.

Le classi che utilizziamo sono:
FileWriter (accetta un File o una String) e fornisce i metodi write(), flush(), close().
BufferedWriter (accetta un Writer) e fornisce come FileWriter e in più newLine().
PrintWriter (accetta File, String, Writer e OutputStream) e fornisce i metodi di FileWriter e inoltre format(), printf(), print(), println().
FileReader (accetta un File o una String) e fornisce read().
BufferedReader (accetta un Reader) e fornisce read() e readLine();

Una classe che può essere utile conoscere (non per la certificazione, ma per se stessi) è InputStreamReader; questa classe è un Reader, e accetta un InputStream; in pratica fa da ponte tra i due sistemi e permette di usare BufferedReader su oggetti InputStream (che sono quasi tutti quelli restituiti dai flussi: System.in, Socket, Servlet, ...).

La lettura (da file, da flusso, da console) si può ottenere usando:

BufferedReader brF = new BufferedReader(new FileReader("nomefile.txt"));
BufferedReader brS = new BufferedReader(new InputStreamReader(socket.getInputStream())); //socket è qui per esempio, non esiste nel contesto
BufferedReader brC = new BufferedReader(new InputStreamReader(System.in));
quindi si può utilizzare brF.readLine(); poiché readLine restituisce null quando il flusso termina, si può usare il costrutto comune:
String res;
while ((res = brF.readLine()) != null) { /* fai qualcosa con res*/ }
La scrittura (ovunque, o su file in modalità append) si ottiene usando:
PrintWriter pwF = new PrintWriter("nomefile.txt");
PrintWriter pwFA = new PrintWriter(new FileWriter("nomefile.txt", true));
PrintWriter pwS = new PrintWriter(socket.getOutputStream()); //socket è qui per esempio, non esiste nel contesto
quindi si scrive usando pwF.println("ciao mondo"); o pwF.format("che valore ha %d?", 123);
è importante ricordare che se non si chiude il flusso e soprattutto se non si fa il flush() dopo aver scritto si rischia di non mandare realmente i dati.
L'utilizzo di Console permette di utilizzare tastiera e monitor per l'output; in alcuni casi non si dispone di una Console (vedi usando Eclipse) e in quel caso si deve implementare a mano il codice di lettura da tastiera; altrimenti possiamo usare codice come il seguente:
    Console c = System.console();
    if (c != null) {
        System.out.println("Dammi il tuo username: ");
        String username = c.readLine();
        System.out.println("Ora dammi la password: ");
        char [] pw = c.readPassword();
        System.out.println("La tua username è " + username);
        System.out.print("La tua password è ");
        for (char ch : pw)
            c.format("%c", ch);
        c.format("\n");
    }
che si occupa di leggere da tastiera una riga, leggere una password (si usano i caratteri perché le stringhe sono immutabili in Java, e non sarebbe saggio usare un oggetto immutabile e in una pool per memorizzare una password) e poi usa il metodo format che, tramite la sintassi che troviamo anche in C (printf) permette di stampare stringhe formattate.

Infine abbiamo le classi Java che gestiscono la Serializzazione.
Un oggetto si dice Serializable se implementa l'interfaccia Serializable. L'utilità deriva dal fatto che si potrà scrivere quella istanza come flusso di byte, e si potrà inviare via Stream, o scrivere su file, o inserirlo su database; le classi che usiamo sono:
DataInputStream / DataOutputStream - forniscono metodi per leggere e scrivere dati primitivi o stringhe UTF;
FileInputStream / FileOutputStream - forniscono metodi per accedere a file come stream di byte (e non stream di caratteri);
ObjectInputStream / ObjectOutputStream - permettono di leggere e scrivere dati primitivi o oggetti;

Vediamo alcuni esempi relativi a questi oggetti:

System.out.println("Inserisci un numero tra 1 e 100");
DataInputStream dis = new DataInputStream(System.in);
int value = dis.readInt();
System.out.println(value);
esempio di codice che potrebbe trarvi in inganno, poiché readInt() non si comporta come potremmo pensare; non prende una linea, la converte in intero e la restituisce, bensi legge 4 byte e restituisce un valore intero con quei byte; questo codice potrebbe capitare come domanda, quindi bisogna imparare la logica richiesta;


class Nonno implements Serializable {
public String nome;
}
...
    Nonno n = new Nonno();
    n.nome = "pippo";
    new ObjectOutputStream(new FileOutputStream("data.txt")).writeObject(n);
...

scrive l'istanza di Nonno indicato sul file "data.txt" in formato binario; per ottenere indietro il dato bisogna eseguire l'operazione inversa:
n = (Nonno)new ObjectInputStream(new FileInputStream("data.txt")).readObject();

La serializzazione è un processo complesso e oneroso per il sistema, considerato che per ogni oggetto bisogna serializzare ogni sua variabile, fino ad avere solo tipi primitivi.
Il seguente codice lancia eccezione java.io.NotSerializableException sulla riga di writeObject
    class NonnoNonSerializabile implements Serializable {
        public String nome;
        public Object surname;
    }
...
    NonnoNonSerializabile n = new NonnoNonSerializabile();
    n.nome = "pippo";
    n.surname = new Object();
    new ObjectOutputStream(new FileOutputStream("data.txt")).writeObject(n);
...
I problemi con la serializzazione iniziano quando non abbiamo modo di rendere tutte le classi serializzabili; in quel caso possiamo solo rendere transient il campo colpevole, e implementare a mano il codice:
    private void writeObject(ObjectOutputStream os) {
        // codice per salvare il soprannome del nonno
    }
    private void readObject(ObjectInputStream is) {
        // codice per leggere il soprannome e memorizzarlo sul nonno
    }
così si risolve il problema delle classi non Serializable;

Dettagli importanti sulla serializzazione sono:
  • le variabili statiche non vengono mai serializzate.
  • se in una gerarchia di classi una classe non è serializabile, quando si ripristina l'oggetto verrà chiamato il costruttore di quella classe (e quindi di tutti i padri);
  • gli oggetti non serializabili devono essere marcati transient (o riceveremo una eccezione)
  • gli array sono serializabili, ma deve esserlo anche il loro contenuto
Gli esempi di codice mostrato si trovano qui

Nessun commento:

Posta un commento