Streams e Input/output • Scansione e Formattazione di un testo • Streams Testuali e Binarie • Streams di dati primitivi • Streams di oggetti (serializzazione) • File ad accesso diretto Scansione Scanner • Da Java 1.5 il modo più semplice di leggere da una stream di tipo testo: Uno scanner suddivide la stream in tokens utilizzando delimitatori (per default spazi, tabs, newlines …) i tokens possono essere convertiti in valor di tipo primitivo e stringa utilizzando i metodi della classe Scanner in = new Scanner(File(“input.txt“)); Continua… Scansione Scanner • Per effettuare la scansione dell’input utilizziamo i metodi forniti dalla classe • Metodi di scansione generica String next(), bool hasNext() cercano il prossimo token (ignorando i delimitatori) • Metodi di scansione specifica per tipo int nextInt(), bool hasNextInt() ... Continua… Scansione Scanner • non solo per input da file … Continua… Scansione Output Formattato • Per formattare un file di testo costruiamo un PrintWriter sul file PrintWriter out = new PrintWriter("output.txt"); • Se il file esiste al momento della creazione il contenuto viene cancellato • Se il file non esiste, la chiamata al costruttore crea un nuovo file, vuoto Continua… Output Formattato • Utilizziamo print e println per scrivere con PrintWriter: out.println(29.95); out.println(new Rectangle(5, 10, 15, 25)); out.println("Hello, World!"); • File aperti (in scrittura) devono essere sempre chiusi out.close(); • Altrimenti non abbiamo garanzie che l’output sia effettuato Continua… Output Formattato PrintWriter • non solo per output sul file … Continua… Output Formattato PrintWriter out = new PrintWriter("output.txt"); Esempio • Legge tutte le linee di un file (di tipo testo) e le copia in output precedute dal numero di linea Mary had a little lamb Whose fleece was white as snow. And everywhere that Mary went, The lamb was sure to go! /* /* /* /* 1 2 3 4 */ */ */ */ Mary had a little lamb Whose fleece was white as snow. And everywhere that Mary went, The lamb was sure to go! Continua… File LineNumberer.java import import import import java.io.FileReader; java.io.IOException; java.io.PrintWriter; java.util.Scanner; public class LineNumberer { public static void main(String[] args) { Scanner console = new Scanner(System.in); System.out.print("Input file: "); String inputFileName = console.next(); System.out.print("Output file: "); String outputFileName = console.next(); try { Continua… File LineNumberer.java FileReader reader = new FileReader(inputFileName); Scanner in = new Scanner(reader); PrintWriter out = new PrintWriter(outputFileName); int lineNumber = 1; while (in.hasNextLine()) { String line = in.nextLine(); out.println("/* " + lineNumber + " */ " + line); lineNumber++; } out.close(); } catch (IOException exception) { System.out.println("Error processing file:"+ exception); } } } Domande • Che succede se forniamo lo stesso nome per i due file di input e output? • Che succede se il nome fornito dall’utente non corrisponde ad alcune file? Risposte • Creando il PrintWriter si azzera il contenuto del file di output. Anche il file di input è quindi nullo, e dunque il ciclo termina immediatamente. • Il programma cattura una FileNotFoundException, stampa un messaggio di errore e termina. Streams • Due modalità di memorizzazione e codifica dei dati: formato testo formato binario • Due corrispondenti gerarchie di streams nella libreria java.io Streams di Testo • Utilizzate per la lettura scrittura di testo • Sono sequenze di caratteri l’intero 12.345 è memorizzato come la seguenza di caratteri '1' '2' '3' '4' '5' Continua… Streams di Testo • Reader, Writer classi astratte che fanno da radici per gerarchia delle stream di testo per le operazioni di input e output • Molte sottoclassi, alcune già viste FileReader, Scanner, ... FileWriter, PrintWriter ... Continua… Streams di Testo • Le classi Reader, Writer forniscono i metodi base per l’input/output • Reader.read() restituisce il prossimo carattere, come intero -1 se non esiste un nuovo carattere (nd of file) si blocca in attesa di input da tastiera Reader reader = new FileReader(“input.txt”); int next = reader.read(); char c; if (next != -1) c = (char) next; Continua… Streams di Testo • Le classi Reader, Writer forniscono i metodi base per l’input/output • Write.write(char c) scrive il carattere sulla stream Streams Binarie • Utilizzate la manipolazione di dati in formato binario • Dati rappresentati come sequenze di bytes l’intero 12.345 è memorizzato come la seguenza di quattro bytes 0 0 48 57 • Rappresentazione più compatta ed efficiente Continua… Streams Binarie • InputStream, OutputStream classi astratte che fanno da radici per gerarchia delle stream binarie per le operazioni di input e output • Anche qui molte sottoclassi concrete FileInputStream, ObjectInputStream, ... FileOutputStream,ObjectOutputStream, ... Continua… Streams Binarie • Le classi InputStream, OutputStream forniscono i metodi base per l’input/output • InputStream.read() restituisce il prossimo carattere, come intero -1 se non esiste un nuovo carattere (end of file) • OutputStream.write(byte b) scrive b sullo stream Continua… DataStreams • Utilizzate per leggere e scrivere in formato binario di tipi primitivi byte, char, short, ..., double, boolean, • Due classi: DataInputStream, DataOutputStream • Forniscono metodi per manipolare dati primitivi, readBoolean / writeBoolean readChar / writeChar readInt / writeInt ... / ... Input/output strutturato • Per operazioni più complesse utilizziamo le sottoclassi, come classi decorators Reader reader = new FileReader(“input.txt”)); Scanner input = new Scanner(reader); Writer writer = new FileWriter(“output.txt”)); PrintWriter output = new PrintWriter(writer)); InputStream is = new FileInputStream(“in.bin”)); DataInputStream dis = new DataInputStream(is); OutputStream os = new FileOutputStream(“out.bin”)); DataOutputStream dos = new DataOutputStream(os); DataOutputStreams • Scrive un array di double su un file void writeData(double[] data, String file) throws IOException { DataOutputStream out = new DataOutputStream( new FileOutputStream(file)); out.writeInt(data.length()); for (int i = 0; i < data.length; i++) out.writeDouble(data[i]); out.close(); } DataInputStreams • Legge un file di double, in cui il primo elemento è un intero che rappresenta il numero di valori contenuti nel file • restituisce un array con i valori letti double[] readData(String file) throws IOException { DataInputStream in = new DataInputStream( new FileInputStream(file)); double[] data = new double[in.readInt()]; for (int i = 0; i < data.length; i++) data[i] = in.readDouble(); in.close(); return data; } File Streams • Permettono di utilizzare files come streams • Due coppie di classi FileInputStream / FileOutputStream FileReader / FileWriter • Ognuna di queste classi permette di costruire una stream su un file a partire da: una stringa, il nome del file un oggetto di tipo File un oggetto di tipo FileDescriptor La classe File • Rappresentazione astratta del nome e proprietà di files e directories. • Diversi sistemi operativi utilizzano diverse sintassi per i nomi di files: la classe File fornisce una rappresentazione indipendente dal sistema • Un nome è rappresentato da una stringa prefissa che rappresenta la radice la radice del file system : ad esempio “/” in unix e “\\” per una sequenza di zero o più nomi, ciascuno dei quali rappresenta un passo sul cammino al file. Tutti i nomi sono directories, ad eccezione dell’ultimo che può essere una directory o un file ObjectStreams • Estensioni delle DataStreams a valori di tipo riferimento • Classi: ObjectInputStream e ObjectOutputStream • serializzazione/deserializzazione conversione di un oggetto in/da una stream di bytes • Metodi ObjectOutputStream.writeObject() ObjectInputStream.readObject() ObjectStreams • writeObject(Object obj): scrive sulla stream una sequenza di bytes corrispondente al grafo che costituisce obj il grafo include tutti i campi primitivi dell’oggetto e tutti gli oggetti raggiungibili da riferimenti presenti in obj; esclusi i campi marcati transient • Object readObject() legge dalla stream una sequenza di bytes che rappresenta un grafo e restituisce l’oggetto corrispondente writeObject() • Scrive un intero oggetto sulla stream BankAccount b = . . .; ObjectOutputStream out = new ObjectOutputStream( new FileOutputStream("bank.dat")); out.writeObject(b); • Anche una lista viene scritta da una sola writeObject ArrayList<BankAccount> a = new ArrayList<BankAccount>(); // ... aggiungi BankAccounts ad a ... out.writeObject(a); readObject() • Restituisce un riferimento di tipo Object Necessario ricordare i tipi degli oggetti scritti sulla stream per leggerli ed utilizzare cast BankAccount b = (BankAccount) in.readObject(); • può lanciare ClassNotFoundException eccezione controllata necessario catturare o dichiarare Continued… Serializable • Oggetti scritti su una object stream devono appartenere a classi che implementano l’interfaccia Serializable class BankAccount implements Serializable { . . . } • Serializable è una interfaccia marker non ha metodi, segnala una proprietà. Continua… Serializable • Ogni oggetto scritto sulla stream viene associato ad un numero seriale • Se lo stesso oggetto viene scritto due volte, la seconda volta si scrive solo il numero seriale, non l’oggetto • In fase di lettura, ogni input alloca un nuovo oggetto, a meno che la lettura non incontri un numero seriale duplicato, nel qual caso si restituisce un riferimento allo stesso oggetto File Serialtester.java public static void main(String[] args) throws IOException, ClassNotFoundException { Bank bank; File f = new File("bank.dat"); if (f.exists()) { ObjectInputStream in = new ObjectInputStream (new FileInputStream(f)); bank = (Bank) in.readObject(); in.close(); } else { bank = new Bank(); bank.addAccount(new BankAccount(1001, 20000)); bank.addAccount(new BankAccount(1015, 10000)); } Continua… File Serialtester.java // Esegui un deposito BankAccount a = firstBankOfJava.find(1001); a.deposit(100); System.out.println(a.getAccountNumber() + ":" + a.getBalance()); a = firstBankOfJava.find(1015); System.out.println(a.getAccountNumber() + ":" + a.getBalance()); ObjectOutputStream out = new ObjectOutputStream (new FileOutputStream(f)); out.writeObject(firstBankOfJava); out.close(); } } Serializzazione user-defined • In alcune situazioni la serializzazione default può risultare scorretta/inefficiente. class Nome implements Serializable { private String nome; private transient int hash; public Nome (String nome) { this.nome = nome; hash = nome.hashCode();} . . . } • hash è transient, perché si può calcolare dal nome Continua… Serializzazione user-defined class Nome implements Serializable { private String nome; private transient int hash; public Nome (String nome) { this.nome = nome; hash = nome.hashCode();} . . . } • Il campo hash è transient, • Ma allora deserializzando un Nome perdiamo il suo codice hash … Continua… Serializzazione user-defined • Sia C la nostra classe Serializable • Definiamo in C due metodi: void writeObject(ObjectOutputStream) void readObject(ObjectInputStream) • Invocati automaticamente da ObjectOutputStream.writeObject(Object o) quando l’argomento ha tipo C ObjectInputStream.readObject()per leggere argomenti scritti da C.writeObject(ObjectOutputStream) Nome: serializzazione corretta class Nome implements Serializable { private String nome; private transient int hash; public Nome (String nome) { this.nome = nome; hash = name.hasCode();} private void writeObject(ObjectOutputStream out) throws IOException { out.writeUTF(nome); } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { nome = in.readUTF(); hash = nome.hashCode();} } Continua… Nome: serializzazione corretta • writeObject / readObject dichiarano IOException invocano metodi che potrebbero lanciarle. • readObject dichiara ClassNotFoundException : deserializzazione richiede la costruzione oggetti di classi che devono essere caricate (e quindi trovate) per invocarne i costruttori • sono private: per protezione il processo di serializzazione bypassa questo vincolo, per invocare comunque i due metodi, utilizzando i meccanismi di riflessione per disattivare i livelli di accesso File ad Accesso Casuale • Permette di accedere a punti arbitrari di un file • Solo per file memorizzati su disco System.in e System.out non supportano queste operazioni • Ogni file su disco ha uno stato che determina la posizione corrente di lettura e/o scrittura Accesso Casuale RandomAccessFile • Possiamo aprire un file in diverse modalità Read-only ("r") Read-write ("rw") Write-only ("w") RandomAccessFile f = new RandomAcessFile("bank.dat","rw"); • Il metodo seek() permette di spostare il filepointer ad una specifica posizione f.seek(n); Continua… RandomAccessFile • f.getFilePointer() restituisce la posizione corrente del file pointer // tipo "long" perchè un file può avere grandi dimensioni long n = f.getFilePointer(); • f.length() restituisce la lunghezza del file fileLength = Continua… RandomAccessFile • Memorizza dati in formato binario • readInt e writeInt leggono e scrivono interi su 4 bytes • readDouble e writeDouble utilizzano 8 bytes Esempio • Utilizziamo un file ad accesso casuale per memorizzare un insieme di conti bancari memorizziamo solo la parte dati di ciascun conto numero di conto e saldo • Il programma permette di selezionare un conto ed eseguire operazioni di deposito/prelievo … Esempio • Calcolo del numero di conti sul file public int size() throws IOException { return (int) (file.length() / RECORD_SIZE); // RECORD_SIZE = 12 bytes: // 4 bytes per il numero // 8 bytes for il saldo } Esempio • Accesso (in lettura) all’n-esimo conto sul file public BankAccount read(int n) throws IOException { file.seek(n * RECORD_SIZE); int accountNumber = file.readInt(); double balance = file.readDouble(); return new BankAccount(accountNumber, balance); } Esempio • Accesso (in scrittura) all’n-esimo conto public void write(int n, BankAccount account) throws IOException { file.seek(n * RECORD_SIZE); file.writeInt(account.getAccountNumber()); file.writeDouble(account.getBalance()); } File BankDatatester.java import java.io.IOException; import java.io.RandomAccessFile; import java.util.Scanner; /** Test per file ad accesso diretto. Un loop di interazione con l’utente in cui si agisce su un conto esistente o se ne crea uno nuovo.. */ public class BankDataTester { public static void main(String[] args) throws IOException { Scanner in = new Scanner(System.in); BankData data = new BankData(); try Continua… File BankDatatester.java { data.open("bank.dat"); boolean done = false; while (!done) { System.out.print("Account number: "); int accountNumber = in.nextInt(); System.out.print("Amount to deposit: "); double amount = in.nextDouble(); int position = data.find(accountNumber); BankAccount account; if (position >= 0) { account = data.read(position); Continued… account.deposit(amount); File BankDatatester.java System.out.println("new balance=" + account.getBalance()); } else // Add account { account = new BankAccount(accountNumber, amount); position = data.size(); System.out.println("adding new account"); } data.write(position, account); System.out.print("Done? (Y/N) "); String input = in.next(); if (input.equalsIgnoreCase("Y")) done = true; } } Continua… File BankDatatester.java finally { data.close(); } } } File BankData.java import java.io.IOException; import java.io.RandomAccessFile; /** * Una classe associata ad un file contenente i dati . */ public class BankData { /** * Costruisce una BankData senza associarla ad un file */ public BankData() { file = null; } Continua… File BankData.java /** * Apre il file di dati. * @param il nome del file con i dati */ public void open(String filename) throws IOException { if (file != null) file.close(); file = new RandomAccessFile(filename, "rw"); } Continua… File BankData.java /** * Restituisce il numero di conti sul file */ public int size() throws IOException { return (int) (file.length() / RECORD_SIZE); } /** * Chiude il file */ public void close() throws IOException { if (file != null) file.close(); file = null; } Continua… File BankData.java /** * Legge un record dal file. */ public BankAccount read(int n) throws IOException { file.seek(n * RECORD_SIZE); int accountNumber = file.readInt(); double balance = file.readDouble(); return new BankAccount(accountNumber, balance); } Continua… File BankData.java /** * Trova la posizione del conto con il dato numero * @result = la posizione, -1 se il conto non viene trovato */ public int find(int accountNumber) throws IOException { for (int i = 0; i < size(); i++) { file.seek(i * RECORD_SIZE); int a = file.readInt(); if (a == accountNumber) // trovato return i; } return -1; // nessun match sull’intero file } Continua… File BankData.java /** Salva un record sul tile @param n l’indice del conto sul file @param il conto da salvare */ public void write(int n, BankAccount account) throws IOException { file.seek(n * RECORD_SIZE); file.writeInt(account.getAccountNumber()); file.writeDouble(account.getBalance()); } private RandomAccessFile file; Continua… File BankData.java public static final int INT_SIZE = 4; public static final int DOUBLE_SIZE = 8; public static final int RECORD_SIZE = INT_SIZE + DOUBLE_SIZE; } Output Account number: 1001 Amount to deposit: 100 adding new account Done? (Y/N) N Account number: 1018 Amount to deposit: 200 adding new account Done? (Y/N) N Account number: 1001 Amount to deposit: 1000 new balance=1100.0 Done? (Y/N) Y Esempio: Text Editor Grafico MenuItem Menu MenuBar MenuItem Menu Menu Items • Aggiungiamo i menu items e i menu in cascata con il metodo add add(): JMenuItem fileExitItem = new JMenuItem("Exit"); fileMenu.add(fileExitItem); • Un menu item genera eventi eventi, più precisamente ActionEvents Continua… Menu Items • Aggiungiamo quindi un listener a ciascun item: fileExitItem.addActionListener(listener); • Il listener viene associato agli items del menu, non al menu o alla barra dei menu Struttura dei Menu Items ActionListener <interfaccia> EditorMenuItem <abstract> ClearMenuItem PasteMenuItem JMenuItem FileMenuItem <abstract> FindMenuItem QuitMenuItem : OpenMenuItem SaveMenuItem EditorMenuItem /** EditorMenuItem: un menu item generico */ abstract class EditorMenuItem extends JMenuItem implements ActionListener { // il buffer su cui opera il menu item private EditBuffer buffer; public EditorMenuItem(String label, EditBuffer buff) { super(label); buffer = buff; addActionListener(this); } protected EditBuffer getBuffer() { return buffer; } // no actionPerformed(): la classe e` abstract } Clear/Cut/Copy/Paste class ClearMenuItem extends EditorMenuItem { public ClearMenuItem(String label, EditBuffer buff) { super(label, buff); } public void actionPerformed(ActionEvent e) { getBuffer().clear(); } } class CutMenuItem extends EditorMenuItem { public CutMenuItem(String label, EditBuffer buff) { super(label, buff); } public void actionPerformed(ActionEvent e) { getBuffer().cut(); } } Continua… Clear/Cut/Copy/Paste class CopyMenuItem extends EditorMenuItem { public CopyMenuItem(String label, EditBuffer buff) { super(label, buff); } public void actionPerformed(ActionEvent e) { getBuffer().copy(); } } class PasteMenuItem extends EditorMenuItem { public PasteMenuItem(String label, EditBuffer buff) { super(label, buff); } public void actionPerformed(ActionEvent e) { getBuffer().paste(); } } Find /** * FindMenuItem: genera un dialog per cercare * una stringa nella text area */ class FindMenuItem extends EditorMenuItem { public FindMenuItem(String label, EditBuffer buff) { super(label, buff); } // . . . metodo actionPerformed . . . } Continua… Find public void actionPerformed(ActionEvent e) { String s = JOptionPane.showInputDialog(this,"Search:"); if ( s != null ) { int index = buffer.findFromCaret(s); if ( index == -1 ) { int response = JOptionPane.showConfirmDialog(this, "Not found. Wrapped Search?"); if ( response == JOptionPane.YES_OPTION ) { index = buffer.findFromStart(s); if ( index == -1 ) JOptionPane.showMessageDialog(thi "Not Found"); } } } Quit /** QuitMenuItem: esci dall'editor */ class QuitMenuItem extends JMenuItem implements ActionListener { private EditFrame frame; public QuitMenuItem(String label, EditFrame frame) { super(label); this.frame = frame; addActionListener(this); } public void actionPerformed(ActionEvent e) { if (frame.confirmQuit()) System.exit(0); return; } } FileMenuItem abstract class FileMenuItem extends EditorMenuItem { public FileMenuItem(String label, EditBuffer buff) { super(label, buff); } public File chooseFile() { File chosen = null; JFileChooser chooser = new JFileChooser(); chooser.setDialogTitle("Open"); int result = chooser.showDialog(null,“Open"); if ( result == JFileChooser.APPROVE_OPTION ) { chosen = chooser.getSelectedFile(); } return chosen; } } JFileChooser Open class OpenMenuItem extends FileMenuItem { public OpenMenuItem(String label, EditBuffer buff) { super(label, buff); } public void actionPerformed(ActionEvent e) { File f = chooseFile(); if ( f == null) return; getBuffer().setFile(f); try { getBuffer().setText(""); getBuffer().openFile(); } catch (IOException exc) { JOptionPane.showMessageDialog(null, "Could not open File"); } } } EditBuffer public class EditBuffer extends JTextArea { private File f; // il file associato al buffer public EditBuffer(File initFile, int rows, int cols) { super("", rows, cols); f = initFile; setLineWrap(true); setFont(new Font("Courier", Font.PLAIN, 14)); if (f == null) return; try { openFile(); } catch (IOException e) { JOptionPane.showMessageDialog(this,"File not Found!"); } } . . . Continua… EditBuffer public void openFile() throws IOException { Scanner opened = null; try { FileReader r = new FileReader(f); opened = new Scanner(r); while ( opened.hasNextLine() ) { String s = opened.nextLine(); append(s+"\n"); } } finally { if (opened != null) opened.close(); } } Continua… EditBuffer public void saveBuffer() throws IOException, NoFileSelectedException { PrintWriter p = null; if (f == null) throw new NoFileSelectedException(); try { FileWriter w = new FileWriter(f); p = new PrintWriter(w); p.print(getText()); } finally { if (p != null) p.close(); } }