ATTENZIONE: il nuovo sito di riferimento per questa guida e altre cose più
o meno interessanti è
il mio BLOG
In java.io
ci sono due gruppi principali di classi, uno che lavora
con i byte, e uno che gestisce i caratteri
unicode.
Queste classi sono abbastanza generiche da permettere di lavorare con molte
entità diverse - file, connessioni di rete, buffer in memoria, pipe, ecc.
- anche se in questo capitolo ci si concentrerà sui file.
Il modo più semplice per leggere un file di testo è quello di usare
la nuova (da java 1.5) classe java.util.Scanner.
Come esempio il seguente programma stampa a video
il file di testo passato come primo argomento:
import java.io.*; import java.util.*; class JCat { public static void main(String[] args) throws Exception // ATTENZIONE! { Scanner sc = new Scanner(new File(args[0])); sc.useDelimiter("\n"); while(sc.hasNext()) System.out.println(sc.next()); } }Scanner è un Iterator, come si può vedere dalle ultime due linee.
Un'altra classe un po' "alternativa" è java.io.RandomAccessFile, molto più familiare delle classi "canoniche" per chi proviene dal C, e adatta ad aprire file in lettura e scrittura ("rw") o in sola lettura ("r"):
RandomAccessFile f = new RandomAccessFile("file.txt", "rw"); f.writeUTF("File ad accesso casuale.\n"); f.seek(0); // va all'inizio f.writeUTF("abcd"); // sostituisce 'File' con 'abcd' f.close();
Reader e Writer sono al vertice di due gerarchie di classi che si occupano rispettivamente della lettura e della (sovra)scrittura di char.Partiamo direttamente con un esempio, la lettura e stampa del file "nomefile.txt":
String s; BufferedReader reader = new BufferedReader( new FileReader("nomefile.txt") ); 5 while( (s = reader.readLine()) != null ) System.out.println(s); reader.close();Con le linee 2-4 è stato creato un oggetto FileReader a partire dal nome del file da leggere, che a sua volta viene usato per creare un BufferedReader. Questo "wrap" serve ad attivare una cache, che permette un notevole incremento di prestazioni.
Vediamo ora l'output:
PrintWriter writer = new PrintWriter( new BufferedWriter( new FileWriter("nomefile.txt", true))); // non sovrascrivere, accoda writer.print("Questo "); writer.println("e' un file di testo.");In questo caso si è aperto il file in "append" (java 1.4+). È stato usato PrintWriter per facilitare la scrittura del testo.
Quando è necessario lavorare con i byte le classi derivate da Reader e Writer non sono più adatte: servono quelle delle famiglie InputStream e OutputStream (oppure, solo per i file, RandomAccessFile).
I nomi delle classi e il loro funzionamento sono tuttavia molto simili - ad esempio la classe corrispondente a FileReader è FileInputStream. Esistono anche delle classi per la conversione, in una direzione:
reader = new InputStreamReader(is); writer = new OutputStreamWriter(os);Due classi non hanno corrispondenti nella famiglia "char oriented", DataInputStream e DataOutputStream:
DataOutputStream do = new DataOutputStream( new BufferedOutputStream( new FileOutputStream("nomefile.dat", 5 true))); // append do.writeInt(43210); do.close();In questo pezzo di codice viene aperto un file su cui viene scritto il valore dell'intero 43210 (4 byte).
Similmente si lavora con DataInputStream.
System.in è un InputStream collegato allo standard input. Vediamo allora come ottenere dall'utente un numero intero immesso da tastiera:
Scanner stdin = new Scanner(System.in); int i; System.out.print("Inserisci un intero: "); while(true) try { i = stdin.nextInt(); break; // niente eccezione, abbiamo il numero } catch(InputMismatchException e) { System.out.println("Devi inserire un int!"); stdin.next(); // "scarta" l'elemento } System.out.println("Hai immesso l'intero: " + i);Attenzione, in Italia il punto è un LocalGroupSeparator - vedi
java.text.DecimalFormatSymbols.getGroupingSeparator()
-
che separa le migliaia: 123.456 diventa così un int, non un float!
L'immissione di stringhe è ancora più semplice:
Scanner stdin = new Scanner(System.in); stdin.useDelimiter("\n"); // prendiamo tutta la riga System.out.print("Inserisci il tuo nome: "); String s = stdin.next(); System.out.println("Il tuo nome e': " + s);
Un OutputStream si può comprimere in modo trasparente, passandolo al costruttore di java.util.zip.GzipOutputStream:
BufferedOutputStream out= new BufferedOutputStream( new GzipOutputStream( // attenzione, sovrascrive! new FileOutputStream("out.dat")));Per leggere da uno stream compresso con GZIP si può usare GzipInputStream.
Esistono anche classi analoghe per la gestione di file in formato ZIP.
La classe java.io.File ha un nome forse fuorviante, dato che non rappresenta un file, ma un percorso - chiamarla "Path" sarebbe stato forse più appropriato. Una volta creato un oggetto File, ad esempio con:
File f = new File(args[0]);posso usarlo per sapere se tale percorso esiste:
System.out.println("Il file " + args[0] + ( f.exists() ? "" : " non") + " esiste");oppure creare la gerarchia di directory nel percorso:
f.mkdirs();eccetera.
Supponiamo di dover scrivere nel file "out.dat" un'istanza della seguente classe - notate che per usare questo meccanismo è necessario implementare Serializable:
class OggettoStrano implements Serializable { String nome; transient String pass; ... }Nota: se si desidera che alcuni campi dell'oggetto non vengano salvati, magari perché contenenti dati sensibili o che non ha senso salvare, bisogna marcarli come transient.
Potremmo procedere così:
OggettoStrano o = new OggettoStrano(); ObjectOutputStream oos = new ObjectOutputStream( new BufferedOutputStream( new FileOutputStream("out.dat"))); // String e' Serializable oos.writeObject("Questo file contiene un oggetto strano."); oos.writeObject(o); // scrivo l'oggetto oos.close(); // chiudo lo streamCome vedete nello stesso stream si possono salvare più oggetti in sequenza - in questo caso una stringa e l'oggetto in questione. Per recuperare questi oggetti al solito si usa la classe simmetrica, ObjectInputStream, e il metodo
readObject()
,
il quale restituisce un Object, su cui si dovrà eseguire il cast appropriato.
Il meccanismo di serializzazione è usato, oltre che per ottenere la persistenza di oggetti, anche per passarli tra diverse JVM, magari dislocate su macchine diverse su una rete, ad esempio usando Java RMI.