Input-Output 16 Maggio 2006 Nei programmi sviluppati sino ad oggi abbiamo usato * output su schermo (con System.out.print e System.out.print) * input da tastiera (con la classe Input) Se un programma deve leggere o scrivere grandi quantità di dati, è conveniente memorizzare questi dati in files. Java fornisce classi e metodi per scrivere e leggere dati da files (nel package java.io) I dati possono essere memorizzati in un file in formato •testo (sequenza di caratteri, leggibile da esseri umani) •binario (sequenza di byte) •Vedremo solo il primo caso (il secondo comunque e’ piu’ efficiente) •Per esempio formato testo per il numero intero 12354, `1` `2``3``5``4` un carattere alla volta Ricordiamo che • Tipo Primitivo: char • Contiene un solo carattere tra apici singoli ‘a’ carattere “a” stringa Il metodo charAt (int i) permette di leggere il carattere in posizione i di una stringa Classi per I/O su files Input da file Output su file Formato testo FileReader FileWriter Formato binario FileInputStream FileOutputStream Queste classi gestiscono I/O di caratteri o bytes da files: per dati più complessi (stringhe, numeri) introdurremo altre classi. I file di testo possono essere aperti, esaminati e modificati usando normali editor (es. emacs). Lettura di singoli caratteri da file Per leggere dati (un carattere alla volta) da un file, occorre: * ricordarsi di importare il pacchetto java.io; import java.io.*; •creare un oggetto della classe FileReader, passando il nome del file al costruttore; FileReader filein = new FileReader("dati.txt"); Per leggere • utilizzare il metodo public int read() della classe FileReader per leggere i caratteri; int next = filein.read(); • Il metodo read() restituisce un intero che può essere: • -1 se si è arrivati alla fine del file; • un intero che rappresenta il codice di un carattere UNICODE. • Tipicamente, si controlla se il numero letto è diverso da -1 e in questo caso si trasforma l'intero in un char usando l'operatore di cast. char c = (char) next; Attenzione •I metodi e il costruttore di FileReader possono lanciare una eccezione di tipo IOException che rappresenta un errore di I/O. Queste eccezioni sono controllate, e quindi devono essere previste dal programmatore. •Quando si termina di leggere o scrivere bisogna “chiudere” il file con il metodo public filein.close(); void close(). Esempio di lettura da file •CopyRead.java: stampa su video del contenuto di un file (copyread.txt). Le eventuali IOException non vengono gestite dal programma ma rinviate al chiamante. •Importante: Cosa succede se non dichiariamo che main può lanciare IOException? •Le eccezioni controllate devono essere riportate nell’intestazione del metodo tramite throws? import java.io.*; public class CopyRead { public static void main(String [] args) throws IOException { // apre il file in lettura FileReader filein = new FileReader("copyread.txt"); int next; char nextc; do { next = filein.read(); // legge il prossimo carattere if (next != -1) { // se non e' finito il file nextc = (char) next; System.out.print(nextc); // stampa il carattere } } while (next != -1); filein.close(); //chiude il file System.out.println(); } } Scrittura di caratteri su file Per scrivere dati su di un file, occorre: •creare un oggetto della classe FileWriter, passando il nome del file al costruttore; *utilizzare *chiudere il metodo public void write(int c) per scrivere i caratteri; il file con il metodo close(). Anche questi metodi possono lanciare una IOException. CopyWrite.java: scrive in un file (copywrite.txt) una stringa fornita dall'utente, un carattere alla volta. import java.io.*; public class CopyWrite { public static void main(String[] args) throws IOException { // apre il file copywrite.txt in scrittura FileWriter fileout = new FileWriter("copywrite.txt"); System.out.print("Scrivi una stringa: "); // legge una stringa da tastiera String str = Input.readLine(); // un ciclo scrive ogni carattere delle stringa nel file for (int i = 0; i < str.length(); i++) fileout.write(str.charAt(i)); fileout.close(); } } // chiude il file Le classi FileReader e FileWriter forniscono i metodi basici per leggere o scrivere caratteri su file. Non è conveniente usarle direttamente nei programmi perché: •rendono un programma inefficiente, visto che ogni operazione di I/O (read o write di singolo carattere) richiede un accesso al file; •non permettono di leggere/scrivere direttamente dati più complessi come stringhe e numeri. Altre classi di Java forniscono funzionalità di I/O più avanzate, ne vedremo alcune Altre classi di Java forniscono funzionalità di I/O più avanzate, in particolare e BufferedWriter usano un buffer (memoria tampone) per memorizzare temporaneamente i caratteri da leggere/scrivere, in modo da ridurre il numero di accessi al file; •BufferedReader fornisce i metodi print e println, che permettono di scrivere qualunque dato Java, convertendolo automaticamente in stringa. •PrintWriter Gli oggetti di queste classi sono dei wrappers: incapsulano gli oggetti delle classi FileReader e FileWriter estendendone le funzionalità. Esempio • Vogliamo scrivere direttamente in un file un numero, un oggetto (il suo stato interno), o una stringa FileWriter fileout = new FileWriter("copyprintwrite.t xt"); // ... che incapsulo in un PrintWriter PrintWriter printout = new PrintWriter(fileout); PrintWriter • Ha metodi print e println per stampare numeri oggetti e stringhe • Allo stesso tempo questi metodi convertono tutto in una stringa, la stringa viene poi scomposta in caratteri e mandata all’oggetto FileWriter corrispondente FileWriter fileout = new FileWriter("copyprintwrite.txt"); // ... che incapsulo in un PrintWriter PrintWriter printout = new PrintWriter(fileout); printout.println(115); //numero printout.println(“Hello”); //Una stringa printout.println(new Studente(“pippo”); //Un oggetto •Nel caso dell’oggetto viene usato ToString(), alla fine nel file e’ memorizzata la Stringa un carattere alla volta Lettura di dati da file •Conviene usare la classe BufferedReader fornisce il metodo readLine() che legge una riga (String) •In pratica continua a leggere nel file di input dell’oggetto lettore relativo tramite read() fino a costruire una stringa •Quando i dati sono terminati readLine() restituisce null •non ha metodi per leggere, ad esempio, interi o double. import java.io.*; public class CopyBufferedRead { public static void main(String [] args) throws IOException { // incapsula in BufferedReader un file aperto in lettura BufferedReader filebuf = new BufferedReader(new FileReader("copyread.txt")); String nextStr; // legge una riga del file nextStr = filebuf.readLine(); while (nextStr != null){ System.out.println(nextStr); // visualizza la riga nextStr = filebuf.readLine(); // legge la prossima riga } filebuf.close(); // chiude il file } } •La differenza rispetto al programma visto prima e’ che legge stringhe, non caratteri Primo esercizio • Scrivere programma che copia il file "inp.txt" nel file "out.txt” un carattere alla volta • Modificarlo in modo che legga una linea alla volta Una soluzione import java.io.*; public class CopiaFile { public static void main(String [] args) throws IOException { // apre il file in lettura FileReader filein=new FileReader("inp.txt"); FileWriter fileout = new FileWriter("out.txt"); int next; char nextc; do { next = filein.read(); // legge il prossimo carattere if (next != -1) { // se non e' finito il file nextc = (char) next; System.out.print(nextc); // stampa il carattere fileout.write(nextc); } } while (next != -1); filein.close(); fileout.close(); //chiude il file System.out.println();}} Secondo Esercizio • Dato un file integers.txt che contiene una sequenza di interi uno per linea • Scrivere un programma che calcola la somma degli interi contenuti nel file • Si può convertire una stringa in un numero usando i metodi statici Integer.parseInt e Double.parseDouble. public class SommaInteri { public static void main(String[] args){ String inputFileName = "./integers.txt"; String line = null; try { BufferedReader in = new BufferedReader(new FileReader(inputFileName)); line = in.readLine(); int somma =0; while(line!=null){ // se non e' possibile convertire la stringa line in intero // viene lanciata un'eccezione NumberFormatException // (catturata sotto con catch apposito) int numero = Integer.parseInt(line); somma += numero; line = in.readLine(); } in.close(); System.out.println(" la somma e' "+ somma); } catch(FileNotFoundException e) { System.out.println(inputFileName+" FileNotFound"); } catch(NumberFormatException e) { System.out.println(" linea non corretta: --> "+line+" <--"); } catch(IOException e) { System.out.println(" IOException "+e); }}} Eccezioni • Si possono verificare varie eccezioni • FileNotFoundException se non si trova il file se la stringa letta nel file non si puo’ convertire in intero • NumberFormaxception Quindi • Sarebbe meglio gestire le eccezioni invece che propagarle • Modificare il programma SommaInteri in modo che quando si verifica un'eccezione venga comunque stampata la somma calcolata fino a quel momento. • 2. Modificare il programma [SommaInteri] in modo che quando si verifica un'eccezione di tipo NumberFormatException dovuta ad una linea del file non convertibile in intero si scarti tale riga ma si prosegua con il calcolo della somma. Esercizio Aggiuntivo Scrivere un programma CatFiles contenente il metodo statico public static void concat(String [] inFiles, String outFile) che concatena il contenuto dei files il cui nome è nell'array inFiles nel file il cui nome è outFile. Se una delle stringhe dell'array inFiles non corrisponde ad un file reale, la stringa viene ignorata e si passa alla prossima. Eventuali altre eccezioni di I/O devono essere catturate dal metodo, che in questo caso deve lanciare una IllegalArgumentException. Per testare il programma, scrivere una semplice classe TestCatFiles che nel main invoca il metodo in questione dopo aver chiesto i nomi dei file all'utente. Controllare che l'effetto sia quello desiderato.