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