In Java, il tipo più comune di operazioni riguardanti l'input/output (I/O) su file e rete è basato su I/O streams, che sono oggetti che supportano i comandi di I/O. Infatti, standard input (System.in) e standard output (System.out) sono esempi di stream di I/O.
Lavorare con file e network richiede familiarità con le eccezioni. Molte delle funzioni che sono utilizzate possono lanciare checked exception, che richiedono la gestione delle eccezioni obbligatoria.
Questo significa in genere chiamare il metodo all'interno di try...catch per gestire l'eccezione qualora fosse sollevata.
I programmi leggono l'input da i data source, sorgenti di dati (tastiera, file, network, o un altro programma) e scrivono l'output su un output di dati, detto data sink (in inglese sink significa lavandino, pozzo), ad esempio il terminale video, un file, un buffer in memoria, o un altro programma. In Java le operazioni di input e output, sono gestite tramite gli stream. Lo stream è un flusso sequenziale e unidirezionale di dati (come l'acqua in un tubo). E' importante notare che Java non differenzia tra diversi tipi di data source, come file o network, keyboard o console. Java riceve i dati da una sorgente aprendo un input stream, e spedisce i dati in un data sink, aprendo un output stream. Tutti gli stream di I/O in Java sono unidirezionali (eccetto java.util.RandomAccessFile). Se il tuo programma ha bisogno di eseguire sia operazioni di input che di output, devi utilizzare due stream, un input stream e un output stream.
Le operazioni sugli stream di I/O si eseguono in tre passi:
Open l'input/output stream associato con il device fisico (es, il file, la console/testiera) costruendo l'istanza appropriata di I/O stream;
Read dall'input stream già aperto fino a incontrare la fine dello stream, o write sull'output stream già aperto ( e eventualmente svuotare (flush) l'output del buffer);
Close l'input/output stream;
Le operazioni di I/O in Java sono più complicate di quelle in C/C++ per supportare l'internazionalizzazione (i18n). Java internamente immagazzina i caratteri (tipi char) in 16 bit in UCS-2 (tipo di codifica). Ma il data source esterno può immagazzinare i caratteri in un altro tipo di codifica (es US-ASCII, ISO-8859-x, UTF-8, UTF-16 o altro tipo), di lunghezza fissa di 8 bit o 16 bit, o di lunghezza variabile di da 1 a 4 byte. Di conseguenza Java deve differenziare tra operazioni di I/O basate sui puri byte (raw bytes) o dati binari (binary data) e operazioni di I/O basate sui caratteri.
Prima di proseguire con gli stream parliamo di file e directory.
File e Directory
La classe java.io.File può rappresentare sia file che directory.
Una path string è usata per individuare un file o una directory. Sfortunatamente, i path string sono system dependent, es: "c:\myproject\java\Hello.java" in Windows o "/myproject/java/Hello.java" in Unix/Mac.
Windows usa il back-slash '\' come directory separator, mentre Unix/Mac utilizzano il forward-slash '/'.
Windows usa 'punto e virgola' (semicolon) ';' come path separator per separare liste di path; mentre Unix utilizza duepunti (colon) ':'.
Windows utilizza "\r\n" come separatore di linea nei file di testo; mentre Unix utilizza '\n' e Mac '\r'.
"c:\" o "\" è chimata la root. Windows supporta molte root, ognuna mappato su un drive (ad esempio "c:\" o "d:\"). Unix e Mac hanno una single root ("\").
Un path può essere assoluto (iniziando dalla root) o relativo (che è relativo a una directory base). le notazioni speciali "." e ".." denotano la directory corrente e la directory padre, rispettivamente.
La classe java.io.File mantiene queste proprietà system-dependent, così da poter scrivere programmi portabili:
Directory separator: i campi static File.separator (come String) e File.charSeparator.
Path separator: i campi static File.pathSeparator (come String) e File.pathSeparatorChar.
Si può costruire una istanza di File con una path string o una URI. Da tener presente che il file/directory potrebbero esistere o non esistere fisicamente. L'URL di un file prende la forma di file://... ad esempio, file:///d:/docs/programming/java/test.html.
publicFile(String pathString)publicFile(String parent,String child)publicFile(File parent,String child)// Constructs a File instance based on the given path string.publicFile(URI uri)// Constructs a File instance by converting from the given file-URI "file://...."
per esempio:
File file =newFile("in.txt"); // A file relative to the current working directoryFile file =newFile("d:\\myproject\\java\\Hello.java"); // A file with absolute pathFile dir =newFile("c:\\temp"); // A director
Verifica delle proprietà di un file o una directory
publicbooleanexists()// Tests if this file/directory exists.publiclonglength()// Returns the length of this file.publicbooleanisDirectory()// Tests if this instance is a directory.publicbooleanisFile()// Tests if this instance is a file.publicbooleancanRead()// Tests if this file is readable.publicbooleancanWrite()// Tests if this file is writable.publicbooleandelete()// Deletes this file/directory.publicvoiddeleteOnExit()// Deletes this file/directory when the program terminates.publicbooleanrenameTo(File dest)// Renames this file.publicbooleanmkdir()// Makes (Creates) this directory.
List directory
publicString[] list()// List the contents of this directory in a String-arraypublicFile[] listFiles()// List the contents of this directory in a File-array
L'esempio sotto lista il contenuto di una certa directory (simile la comando UNIX "ls -r")
// Recursively list the contents of a directory (Unix's "ls -r" command).importjava.io.File;publicclassListDirectoryRecusive {publicstaticvoidmain(String[] args) {File dir =newFile("d:\\myproject\\test"); // Escape sequence needed for '\'listRecursive(dir); }publicstaticvoidlistRecursive(File dir) {if (dir.isDirectory()) {File[] items =dir.listFiles();for (File item : items) {System.out.println(item.getAbsoluteFile());if (item.isDirectory()) listRecursive(item); // Recursive call } } }}
Ora riprendiamo la trattazione con gli stream.
Byte streams
Lettura da InputStream
La classe astratta java.util.InputStream dichiara il metodo astratto read() per leggere byte di dati:
publicabstractintread() throws IOException
Il metodo read():
ritorna l'input byte letto come un intero di valore tra 0 e 255, o
ritorna -1 se si incontra il carattere di "fine stream", o
lancia un eccezione di IOException se incontrato un errore di I/O
Il metodo read() ritorna un intero al posto di un byte, perché utilizza -1 per indicare la fine dello stream.
Il metodo read() blocca finché il byte non è disponibile, o in eccezione di I/O non è sollevata, o la fine dello stream è raggiunta. Il termine blocca vuol dire che il metodo (e il programma) è sospeso. Il programma riprende solo quando il metodo read() ritorna qualcosa.
Due varianti di read() sono implementate in InputStream per leggere blocchi di byte in un array di byte. Esso ritorna il numero di byte letti o -1 se arrivati alla fine dello stream.
publicintread(byte[] bytes,int offset,int length) throws IOException// Read "length" number of bytes, store in bytes array starting from offset of index.publicintread(byte[] bytes) throws IOException// Same as read(bytes, 0, bytes.length)
Scrittura su OutputStream
Simile alla controparte dell'input, la superclasse astratta OutputStream dichiara il metodo astratto write() per scrivere un byte sull'output.
Il byte meno significativo di in int è utilizzato, i 3 byte più significativi sono scartati. Esso lancia una IOException se un errore di I/O avviene (ad esempio lo stream era già stato chiuso).
Come per la read(), due varianti di write() per scrivere blocchi di byte da un array di byte sono disponibili:
publicvoidwrite(byte[] bytes,int offset,int length) throws IOException// Write "length" number of bytes, from the bytes array starting from offset of index.publicvoidwrite(byte[] bytes) throws IOException// Same as write(bytes, 0, bytes.length)
Chiusura di uno stream
Si apre uno stream di I/O, instanziando la classe di stream. Entrambi InputStream e OutputStream sono dotate del metodo close() per chiudere lo stream, che esegue tutte le operazioni per liberare la risorsa a livello di sistema.
publicvoidclose() throws IOException // close this Stream
E' buona pratica esplicitamente chiudere uno stream di I/O, eseguendo la close() nella clausola finally del try-catch-finally per liberare immediatamente le risorse quando abbiamo finito con lo stream. Questo per evitare spreco di risorse. Sfortunatamente, il metodo close() può sollevare anche lui IOException, e dobbiamo racchiuderlo anche lui tra try-catch come possiamo vedere sotto:
FileInputStream in =null;......try { in =newFileInputStream(...); // Open stream............} catch (IOException ex) {ex.printStackTrace();} finally { // always close the I/O streamstry {if (in !=null) in.close(); } catch (IOException ex) {ex.printStackTrace(); }}
Questo non è molto bello e da Java 1.7 è stato introdotto la sintassi try-with-resource, che automaticamente chiude tutte le risorse aperte dopo try-catch. Si vede così un codice molto più pulito:
try (FileInputStream in =newFileInputStream(...)) {............} catch (IOException ex) {ex.printStackTrace();} // Automatically closes all opened resource in try (...).
Flushing dell'OutputStream
In aggiunta la classe OutputStream provvede il metodo flush() per scaricare i rimanenti byte dal buffer di output:
publicvoidflush() throws IOException // Flush the output
Implementazioni di abstact InputStream/OutputStream
Le classi InputStream e OutputStream sono classi astratte che non possono essere instanziate. Bisogna scegliere una sottoclasse concreta per stabilire una connessione a un device fisico. Per esempio puoi instanziare un FileInputStream o FileOutputStream per creare uno stream a un file su disco.
Stream concatenati
Buffered I/O byte stream
I metodi read()/write() in InputStream/OutputStream leggono/scrivono un singolo byte per ogni chiamata. Tutto questo è molto inefficiente, in quanto ogni operazione è eseguita dal sistema operativo (che accede al disco o esegue altre operazioni dispendiose). Il buffering, che legge/scrive un blocco di byte da un device esterno in/dal buffer di memoria in una singola operazione di I/O, è comunemente utilizzato per velocizzare le operazioni di I/O.
FileInputStream e FileOutputStream non sono bufferizzate. Sono spesso concatenate con BufferedInputStream e BufferedOutputStream che provvedono il buffering. Per concatenate gli stream insieme, semplicemente si passa l'istanza di uno stream nel costruttore dell'altro stream. Per esempio il codice seguente concatena un FileInputStream a un BufferedInputStream e finalmente a un DataInputStream:
DataInputStream e DataOutputStream possono essere concatenate con InputStream e OutputStream per parsare byte grezzi e per eseguire operazioni di I/O nel formato desiderato su tipi primitivi come int, double.
Per usare DataInputStream per input formattato, puoi concetenare l'input stream in questo modo:
DataInputStream in =newDataInputStream(new BufferedInputStream(new FileInputStream("in.dat")));
DataInputStream implementa l'interfaccia DataInput, che provvede metodi per leggere tipi primitivi e String:
// 8 PrimitivespublicfinalintreadInt() throws IOExcpetion; // Read 4 bytes and convert into intpublicfinaldoublereadDoube() throws IOExcpetion; // Read 8 bytes and convert into double publicfinalbytereadByte() throws IOExcpetion;publicfinalcharreadChar() throws IOExcpetion;publicfinalshortreadShort() throws IOExcpetion;publicfinallongreadLong() throws IOExcpetion;publicfinalbooleanreadBoolean() throws IOExcpetion; // Read 1 byte. Convert to false if zeropublicfinalfloatreadFloat() throws IOExcpetion;publicfinalintreadUnsignedByte() throws IOExcpetion; // Read 1 byte in [0, 255] upcast to intpublicfinalintreadUnsignedShort() throws IOExcpetion; // Read 2 bytes in [0, 65535], same as char, upcast to intpublicfinalvoidreadFully(byte[] b,int off,int len) throws IOException;publicfinalvoidreadFully(byte[] b) throws IOException;// StringspublicfinalStringreadLine() throws IOException;// Read a line (until newline), convert each byte into a char - no unicode support.publicfinalStringreadUTF() throws IOException;// read a UTF-encoded string with first two bytes indicating its UTF bytes lengthpublicfinalintskipBytes(int n)// Skip a number of bytes
Similmente, si può concatenare DataOutputStream in questo modo:
DataOutputStream out =newDataOutputStream(new BufferedOutputStream(new FileOutputStream("out.dat")));
DataOutputStream implementa l'interfaccia DataOutput, che provvede metodi per scrivere tipi primitivi e String:
// 8 primitive typespublicfinalvoidwriteInt(int i) throws IOExcpetion; // Write the int as 4 bytespublicfinalvoidwriteFloat(float f) throws IOExcpetion;publicfinalvoidwriteDoube(double d) throws IOExcpetion; // Write the double as 8 bytespublicfinalvoidwriteByte(int b) throws IOExcpetion; // least-significant bytepublicfinalvoidwriteShort(int s) throws IOExcpetion; // two lower bytespublicfinalvoidwriteLong(long l) throws IOExcpetion;publicfinalvoidwriteBoolean(boolean b) throws IOExcpetion;publicfinalvoidwriteChar(int i) throws IOExcpetion;// StringpublicfinalvoidwriteBytes(String str) throws IOExcpetion; // least-significant byte of each charpublicfinalvoidwriteChars(String str) throws IOExcpetion;// Write String as UCS-2 16-bit char, Big-endian (big byte first)publicfinalvoidwriteUTF(String str) throws IOException; // Write String as UTF, with first two bytes indicating UTF bytes lengthpublicfinalvoidwrite(byte[] b,int off,int len) throws IOExceptionpublicfinalvoidwrite(byte[] b) throws IOExceptionpublicfinalvoidwrite(int b) throws IOException // Write the least-significant byte
importjava.io.*;publicclassTestDataIOStream {publicstaticvoidmain(String[] args) {String filename ="data-out.dat";String message ="Hi,您好!";// Write primitives to an output filetry (DataOutputStream out =newDataOutputStream(new BufferedOutputStream(new FileOutputStream(filename)))) {out.writeByte(127);out.writeShort(0xFFFF); // -1out.writeInt(0xABCD);out.writeLong(0x1234_5678); // JDK 7 syntaxout.writeFloat(11.22f);out.writeDouble(55.66);out.writeBoolean(true);out.writeBoolean(false);for (int i =0; i <message.length(); ++i) {out.writeChar(message.charAt(i)); }out.writeChars(message);out.writeBytes(message);out.flush(); } catch (IOException ex) {ex.printStackTrace(); }// Read raw bytes and print in Hextry (BufferedInputStream in =newBufferedInputStream(new FileInputStream(filename))) {int inByte;while ((inByte =in.read()) !=-1) {System.out.printf("%02X ", inByte); // Print Hex codes }System.out.printf("%n%n"); } catch (IOException ex) {ex.printStackTrace(); }// Read primitivestry (DataInputStream in =newDataInputStream(new BufferedInputStream(new FileInputStream(filename)))) {System.out.println("byte: "+in.readByte());System.out.println("short: "+in.readShort());System.out.println("int: "+in.readInt());System.out.println("long: "+in.readLong());System.out.println("float: "+in.readFloat());System.out.println("double: "+in.readDouble());System.out.println("boolean: "+in.readBoolean());System.out.println("boolean: "+in.readBoolean());System.out.print("char: ");for (int i =0; i <message.length(); ++i) {System.out.print(in.readChar()); }System.out.println();System.out.print("chars: ");for (int i =0; i <message.length(); ++i) {System.out.print(in.readChar()); }System.out.println();System.out.print("bytes: ");for (int i =0; i <message.length(); ++i) {System.out.print((char)in.readByte()); }System.out.println(); } catch (IOException ex) {ex.printStackTrace(); } }}
Character stream
Java internamente immagazzina i caratteri (char type) in 16 bit codificati con UCS-2 character set. Ma le risorse esterne possono immagazzinare i caratteri anche in altre codifiche (es US-ASCII, , ISO-8859-x, UTF-8, UTF-16, e molte altre), in lunghezza fissa di 8 o 16 bit, o a lunghezza variabile da 1 a 4 byte.
Classi astratte Reader e Writer
A parte l'unità delle operazioni (il char al posto del byte) e la conversione dei character set (operazione complicata), I/O basato sui caratteri è piuttosto identico all'I/O basato sui byte. Al posto di InputStream e OutputStream noi utilizziamo Reader e Writer per stream di caratteri.
FileReader e FileWriter sono implementazioni concrete dell'abstract superclasse Reader e Writer, che supportato operazioni di I/O su disco. FileReader e FileWriter assumono l'encoding di default per le operazioni di I/O.
Buffered I/O Character Stream - BufferedReader & BufferedWriter
BufferedReader e BufferedWriter possono essere concatenati sopra FileReader/FileWriter o altri character stream per eseguire operazioni di I/O bufferizzate invece di operazioni carattere per carattere. BufferedReader ha il metodo readLine(), che legge una riga e ritorna una stringa, senza delimitatore. Le linee possono essere delimitare da '\n' in Unix, "\r\n" in Windows o "\r" su Mac.
importjava.io.*;// Write a text message to an output file, then read it back.// FileReader/FileWriter uses the default charset for file encoding.publicclassBufferedFileReaderWriterJDK7 {publicstaticvoidmain(String[] args) {String strFilename ="out.txt";String message ="Hello, world!\nHello, world again!\n"; // 2 lines of texts// Print the default charsetSystem.out.println(java.nio.charset.Charset.defaultCharset());try (BufferedWriter out =newBufferedWriter(new FileWriter(strFilename))) {out.write(message);out.flush(); } catch (IOException ex) {ex.printStackTrace(); }try (BufferedReader in =newBufferedReader(new FileReader(strFilename))) {String inLine;while ((inLine =in.readLine()) !=null) { // exclude newlineSystem.out.println(inLine); } } catch (IOException ex) {ex.printStackTrace(); } }}
Text File I/O - InputStreamReader and OutputStreamWriter
Si può selezionare il character set tramite il costruttore dell'InputStream.
Une delle cose importanti riguardo il package di I/O è che permette di aggiungere caratteristiche a un oggetto di tipo stream wrappandolo a un altro stream con queste capacità in più. L'oggetto wrapper è a sua volta uno stream, così che si possa scrivere o leggere da esso, ma con in più delle funzionalità aggiuntive rispetto a quelle dello stream di base.
Per esempio, PrintWriter è una sottoclasse di Writer, che provvede metodi utili per scrivere in modo capibile da un essere umano una rappresentazione di tutti i tipi base di Java. Se abbiamo un oggetto di tipo Writer, a qualsiasi sua sottoclasse, e vorremmo usare i metodi di PrintWriter per scrivere l'output su quello stream Writer, tutto quello che dobbiamo fare e wrappare l'oggetto Writer nell'oggetto PrintWriter. Si fa questo costrueno un nuovo oggetto di tipo PrintWriter, utilizzando l'oggetto Writer come parametro del costruttore. Per esempio, se charSink è di tipo Writer, si può fare:
In effetti, il parametro nel costruttore può essere anche un oggetto di tipo OutputStrem o un File, e il costruttore creerà un PrintWriter che scrive sull'output. Quando scriviamo dati sul PrintWriterprintableCharSink, utilizzando i metodi di altro livello della classe, i dati andranno direttamente nello stesso posto come i dati scritti su charSink. Abbiamo creato un'interfaccia migliore alla stessa destinazione. Questo permette di utilizzare i metodi di PrintWriter per inviare dati a un file o alla rete.
Se out è una variabile di tipo PrintWriter, allora i seguenti metodi sono definiti:
out.print(x) - stampa il valore di x, rappresentato nella forma di una stringa di caratteri, sull'ouput stream; x può essere un'espressione di qualsiasi tipo, inclusi sia tipi primitivi che tipi oggetti. Un oggetto è convertito in stringa utilizzando il metodo toString() dell'oggetto. Un valore null è rappresentato con la stringa "null";
out.println() - scrive il fine linea sull'output stream;
out.println(x)
out.printf(formatString, x1, x2, ....)
out.flush()
Da notare che nessuno di questi metodi lancia IOException. Invece la classe PrintWriter, include il metodo:
publicbooleancheckError()
che ritorna true se c'è stato un errore scrivendo i dati sullo stream.
Object Serialization e Object Stream
ObjectInputStream & ObjectOutputStream
ObjectInputStream e ObjectOutputStream possono essere utilizzati per serializzare un oggetto in un byte stream, attraverso questi metodi:
ObjectInputStream e ObjectOutputStream possono essere aggiunti sopra una implementazione concreta di InputStream e OutputStream, come FileInputStream e FileOutputStream.
Per esempio, il seguente codice, scrive oggetti su file su disco. L' estensione ".ser" è una convenzione per i file di oggetti serializzati:
ObjectOutputStream out =newObjectOutputStream(new BufferedOutputStream(new FileOutputStream("object.ser")));out.writeObject("The current Date and Time is "); // write a String objectout.writeObject(newDate()); // write a Date objectout.flush();out.close();
Per leggere e ricostruire gli oggetti all'interno del programma, utilizza il metodo readObject(), che ritorna java.lang.Object. Bisogna poi fare il downcast esplicito all'oggetto desiderato:
ObjectInputStream in =newObjectInputStream(new BufferedInputStream(new FileInputStream("object.ser")));String str = (String)in.readObject();Date d = (Date)in.readObject(); // downcastin.close();
I tipi primitivi e gli array sono per default serializzabili.
Le classi ObjectInputStream e ObjectOutputStream implementano rispettivamente le interfacce DataInput e DataOutput . Si hanno quindi ad esempio i metodi readInt(), readDouble(), writeInt() e writeDouble() per leggere e scrivere i tipi primitivi.
transient & static
i campi static non sono serializzati, poiché appartengono alla classe e non all'istanza che deve essere serializzata;
per prevenire che certi campi siano serializzati, si possono segnare con la parola chiave transient. Questo può ridurre la dimensione di traffico dati;
il metodo writeObject() scrive anche l'informazione sulla classe dell'oggetto, i valori degli attributi non-static e non-transient;
java.io.Serializable & Externalizable Interfaces
Se vogliamo avere una classe che può essere serializzata, la classe deve implementare l'interfaccia java.io.Serializable. L'interfaccia Serializable non dichiara nessun metodo. Interfacce vuote come Serializable sono dette tagging interface (altro esempio è l'interfaccia java.lang.Clonable). Esse identificano le classi che le implementano, il fatto di acquisire certe proprietà, senza richiedere alla classe di implementare alcun metodo.
Molte delle classi standard di Java implementano Serializable, ad esempio tutte la classi wrapper dei tipi primitivi, le classi contenitore, le classi della GUI. In effetti, le sole classi tra quelle standard che non implementano Serializable, sono solo quelle che non avrebbe senso che fossero serializzabili (ad esempio java.io.FileDescriptor, in quanto l'oggetto deserializzato non avrebbe significato in un processo rispetto a quello che l'ha creato).
Array di tipi primitivi o di oggetti serializzabili sono a loro volta serializzabili.
Esempio, l'applicazione ObjectSerializationTest, in cui vengono serializzati e deserializzati oggetti e array del tipo MySerializedObject, da noi definito:
importjava.io.*;publicclassObjectSerializationTest {publicstaticvoidmain(String[] args) {String filename ="object.ser";int numObjs =5;// Write objectstry (ObjectOutputStream out =newObjectOutputStream(new BufferedOutputStream(new FileOutputStream(filename)))) {// Create an array of 10 MySerializedObjects with ascending numbersMySerializedObject[] objs =newMySerializedObject[numObjs];for (int i =0; i < numObjs; ++i) { objs[i] =newMySerializedObject(0xAA+ i); // Starting at AA }// Write the objects to file, one by one.for (int i =0; i < numObjs; ++i) {out.writeObject(objs[i]); }// Write the entire array in one go.out.writeObject(objs);out.flush(); } catch (IOException ex) {ex.printStackTrace(); }// Read raws bytes and print in Hextry (BufferedInputStream in =newBufferedInputStream(new FileInputStream(filename))) {int inByte;while ((inByte =in.read()) !=-1) {System.out.printf("%02X ", inByte); // Print Hex codes }System.out.printf("%n%n"); } catch (IOException ex) {ex.printStackTrace(); }// Read objectstry (ObjectInputStream in =newObjectInputStream(new BufferedInputStream(new FileInputStream(filename)))) {// Read back the objects, cast back to its original type.MySerializedObject objIn;for (int i =0; i < numObjs; ++i) { objIn = (MySerializedObject)in.readObject();System.out.println(objIn.getNumber()); }MySerializedObject[] objArrayIn; objArrayIn = (MySerializedObject[])in.readObject();for (MySerializedObject o : objArrayIn) {System.out.println(o.getNumber()); } } catch (ClassNotFoundException|IOException ex) { // JDK 7ex.printStackTrace(); } }}classMySerializedObjectimplementsSerializable {privateint number;publicMySerializedObject(int number) {this.number= number; }publicintgetNumber() {return number; }}
Una classe potrebbe voler definire un comportamento custom per la serializzazione e la deserializzazione per i propri oggetti, implementando i metodi writeObject(ObjectOutputStream out) e readObject(ObjectInputStreamObject in). Sorprendentemente questi metodi non sono specificati in nessuna interfaccia. Questi metodi devono essere dichiarati private, che è ancora sorprendente, in quanto sono chiamati dall'esterno della classe durante il processo di serializzazione e deserializzazione. Se la classe definisce questi metodi, quello appropriato è invocato da ObjectOutputStream e ObjectInputStream quando un oggetto è serializzato o deserializzato.
Ad esempio, il nostro componente custom potrebbe definire un metodo readObject() per dare l'opportunità di ricalcolare una propria proprietà in fase di deserializzazione (una proprietà dell'oggetto che ad esempio non ha senso che sia serializzata in quanto il valore magari varia e deve essere calcolato in base alla piattaforma su cui è eseguito il programma, ad esempio la dimensione dei font per un programma di grafica). Il metodo potrebbe essere di questo tipo:
privatevoidreadObject(ObjectInputStream in) throws IOException, ClassNotFoundException {in.defaultReadObject(); // Deserialize the component in the usual way.this.computePreferredSize(); // But then go recompute its size. }
Esempio di utilizzo di transient e implementazione di metodi writeObject() e readObject(): esso mostra una classe che implementa un array di numeri non a dimensione fissa. Questa classe definisce un metodo writeObject() per fare qualche operazione di pre-processing prima di essere serializzata e il metodo readObject() per fare operazioni di post-processing dopo la deserializazione.
/* * Copyright (c) 2000 David Flanagan. All rights reserved. * This code is from the book Java Examples in a Nutshell, 2nd Edition. * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. * You may study, use, and modify it for any non-commercial purpose. * You may distribute it non-commercially as long as you retain this notice. * For a commercial use license, or to purchase the book (recommended), * visit http://www.davidflanagan.com/javaexamples2. */importjava.io.*;importjava.util.Arrays;/** * A simple class that implements a growable array of ints, and knows how to * serialize itself as efficiently as a non-growable array. **/publicclassIntListimplementsSerializable {protectedint[] data =newint[8]; // An array to store the numbers.protectedtransientint size =0; // Index of next unused element of array /** Return an element of the array */publicintget(int index) throwsArrayIndexOutOfBoundsException {if (index >= size)thrownewArrayIndexOutOfBoundsException(index);elsereturn data[index]; } /** Add an int to the array, growing the array if necessary */publicvoidadd(int x) {if (data.length== size)resize(data.length*2); // Grow array if needed. data[size++] = x; // Store the int in it. } /** An internal method to change the allocated size of the array */protectedvoidresize(int newsize) {// int[] newdata = new int[newsize]; // Create a new array// System.arraycopy(data, 0, newdata, 0, size); // Copy array elements.int[] newdata =Arrays.copyOf(data, newsize); data = newdata; // Replace old array } /** Get rid of unused array elements before serializing the array */privatevoidwriteObject(ObjectOutputStream out) throwsIOException {if (data.length> size)resize(size); // Compact the array.out.defaultWriteObject(); // Then write it out normally. } /** Compute the transient size field after deserializing the array */privatevoidreadObject(ObjectInputStream in) throwsIOException,ClassNotFoundException {in.defaultReadObject(); // Read the array normally. size =data.length; // Restore the transient field. }}
java.io.Externalizable Interface
L'interfaccia Serializable ha una sotto-interfaccia chiamata Externalizable, che può essere utile se si vuole customizzare il modo in cui una classe è serializzata. Poichè Externalizable estende Serializable, è anche Serializable e si può invocare readObject() e writeObject() di ObjectInputStream e ObjectOutputStream rispettivamente.
ObjectOutput e ObjectInput sono interfacce che sono implementate da ObjectOutputStream e ObjectInputStream, che definiscono i metodi writeObject() e readObject() rispettivamente. Quando un'istanza di Externizable è passata a un ObjectOutputStream, la procedura standard di serializzazione è bypassata; invece lo stream chiama il metodo writeExternal() dell'oggetto. Allo stesso modo, quando un ObjectInputStream legge un Externizable instanza, utilizza readExternal() per ricostruire l'oggetto.
Externizable è utile se si vuole un completo controllo su come un oggetto è serializzato/deserializzato. Ad esempio si potrebbe voler criptare dei dati serializzabili prima di serializzare un oggetto.
Esempio definiamo la classe CompactIntList che implementa Externalizable, e ridefinisce la logica per serializzare e deserializzare per ottenere un formato più compatto:
importjava.io.*;/** * This subclass of IntList assumes that most of the integers it contains are * less than 32,000. It implements Externalizable so that it can define a * compact serialization format that takes advantage of this fact. **/publicclassCompactIntListextendsIntListimplementsExternalizable { /** * This version number is here in case a later revision of this class wants to * modify the externalization format, but still retain compatibility with * externalized objects written by this version **/staticfinalbyte version =1; /** * This method from the Externalizable interface is responsible for saving the * complete state of the object to the specified stream. It can write anything * it wants as long as readExternal() can read it. **/publicvoidwriteExternal(ObjectOutput out) throwsIOException {if (data.length> size)resize(size); // Compact the array.out.writeByte(version); // Start with our version number.out.writeInt(size); // Output the number of array elementsfor (int i =0; i < size; i++) { // Now loop through the arrayint n = data[i]; // The array element to writeif ((n <Short.MAX_VALUE) && (n >Short.MIN_VALUE+1)) {// If n fits in a short and is not Short.MIN_VALUE, then write// it out as a short, saving ourselves two bytesout.writeShort(n); } else {// Otherwise write out the special value Short.MIN_VALUE to// signal that the number does not fit in a short, and then// output the number using a full 4 bytes, for 6 bytes totalout.writeShort(Short.MIN_VALUE);out.writeInt(n); } } } /** * This Externalizable method is responsible for completely restoring the state * of the object. A no-arg constructor will be called to re-create the object, * and this method must read the state written by writeExternal() to restore the * object's state. **/publicvoidreadExternal(ObjectInput in) throwsIOException,ClassNotFoundException {// Start by reading and verifying the version number.byte v =in.readByte();if (v != version)thrownewIOException("CompactIntList: unknown version number");// Read the number of array elements, and make array that bigint newsize =in.readInt();resize(newsize);this.size= newsize;// Now read that many values from the streamfor (int i =0; i < newsize; i++) {short n =in.readShort();if (n !=Short.MIN_VALUE) data[i] = n;else data[i] =in.readInt(); } }}
Random access file
Tutti gli stream di I/O che abbiamo finora trattato sono unidirezionali (one-way). Cioè essi sono o read-only input stream o write-only output stream. Inoltre, essi sono tutti stream solo di accesso sequenziale, utili per leggere o scrivere dati sequenzialmente. Tuttavia, è delle volte necessario leggere il record del file direttamente così come modificare un record esistente come inserire un nuovo record. La classe RandomAccessFile provvede le funzionalità per l'accesso non sequenziale, diretto accesso al file su disco. RandomAccessFile è uno stream bidirezionale (two-way), in quanto supporta entrambe le operazioni, di input e di output, sullo stesso stream.
RandomAccessFile può essere trattato come un grosso array. Possiamo usare un puntatore a file (di tipo long), come all'indice di un array, per accedere a byte individuali o gruppi di byte di tipo primitivi (come int o double). Il puntatore a file è in posizione 0 quando il file è aperto. Esso avanza automaticamente per ogni operazione di read e write per il numero di byte processati.
Nella creazione di un RandomAccessFile, si possono utilizzare i flag 'r' o 'rw' per indicare se l'accesso è in sola lettura o anche in scrittura.
RandomAccessFile f1 =newRandomAccessFile("filename","r");RandomAccessFile f2 =newRandomAccessFile("filename","rw");
i seguenti metodi sono disponibili:
publicvoidseek(long pos) throws IOException;// Positions the file pointer for subsequent read/write operation.publicintskipBytes(int numBytes) throws IOException;// Moves the file pointer forward by the specified number of bytes.publiclonggetFilePointer() throws IOException;// Gets the position of the current file pointer, in bytes, from the beginning of the file.publiclonglength() throws IOException;// Returns the length of this file.
RandomAccessFile non è sottoclasse di InputStream o OutputStream. Tuttavia implementa le interfacce DataInput e DataOutput (simile a DataInputStream e DataOutputStream). Quindi, sono disponibili diversi metodi per leggere e scrivere tipi primitivi su un file, ad esempio:
ThisHello file with few random words.Hello World!Hello Universe!Hello again!Welcome!
Esempi I/O
Esempio - Compressione di file e directory
L'esempio vuole dimostrare un'applicazione interessante delle classi di stream: comprimere file e directory. Le classi utilizzate non sono parte del package java.io ma di java.io.zip. La classe Compress definisce due metodi statici, gzipFile(), che comprime un file utilizzando il formato di compressione GZIP, e zipDirectory() che comprime i file (ma non le directory) in una directory utilizzando il ZIP archive e formato di compressione. gzipFile() usa la classe GZIPInputStream, mentre zipDirectory() usa le classi, ZipOutputStream e ZipEntry, tutte da java.util.zip.
// This example is from _Java Examples in a Nutshell_. (http://www.oreilly.com)// Copyright (c) 1997 by David Flanagan// This example is provided WITHOUT ANY WARRANTY either expressed or implied.// You may study, use, modify, and distribute it for non-commercial purposes.// For any commercial use, see http://www.davidflanagan.com/javaexamplesimportjava.io.*;importjava.util.zip.*;/** * This class defines two static methods for gzipping files and zipping * directories. It also defines a demonstration program as a nested class. **/publicclassCompress { /** Gzip the contents of the from file and save in the to file. */publicstaticvoidgzipFile(String from,String to) throwsIOException {// Create stream to read from the from fileFileInputStream in =newFileInputStream(from);// Create stream to compress data and write it to the to file.GZIPOutputStream out =newGZIPOutputStream(new FileOutputStream(to));// Copy bytes from one stream to the otherbyte[] buffer =newbyte[4096];int bytes_read;while ((bytes_read =in.read(buffer)) !=-1)out.write(buffer,0, bytes_read);// And close the streamsin.close();out.close(); } /** Zip the contents of the directory, and save it in the zipfile */publicstaticvoidzipDirectory(String dir,String zipfile) throwsIOException,IllegalArgumentException {// Check that the directory is a directory, and get its contentsFile d =newFile(dir);if (!d.isDirectory())thrownewIllegalArgumentException("Compress: not a directory: "+ dir);String[] entries =d.list();byte[] buffer =newbyte[4096]; // Create a buffer for copyingint bytes_read;// Create a stream to compress data and write it to the zipfileZipOutputStream out =newZipOutputStream(new FileOutputStream(zipfile));// Loop through all entries in the directoryfor (int i =0; i <entries.length; i++) {File f =newFile(d, entries[i]);if (f.isDirectory())continue; // Don't zip sub-directoriesFileInputStream in =newFileInputStream(f); // Stream to read fileZipEntry entry =newZipEntry(f.getPath()); // Make a ZipEntryout.putNextEntry(entry); // Store entry in zipfilewhile ((bytes_read =in.read(buffer)) !=-1) // Copy bytes to zipfileout.write(buffer,0, bytes_read);in.close(); // Close input stream }// When we're done with the whole loop, close the output streamout.close(); }}
La classe AppCompress per l'esecuzione dell'applicazione:
importjava.io.File;importjava.io.IOException;publicclassAppCompress { /** * Compress a specified file or directory. If no destination name is specified, * append .gz to a file name or .zip to a directory name **/publicstaticvoidmain(String args[]) throwsIOException {if ((args.length!=1) && (args.length!=2)) { // check argumentsSystem.err.println("Usage: java AppCompress <from> [<to>]");System.exit(0); }String from = args[0], to;File f =newFile(from);boolean directory =f.isDirectory(); // Is it a file or directory?if (args.length==2) to = args[1];else { // If destination not specifiedif (directory) to = from +".zip"; // use a .zip suffixelse to = from +".gz"; // or a .gz suffix }if ((newFile(to)).exists()) { // Make sure not to overwrite anythingSystem.err.println("Compress: won't overwrite existing file: "+ to);System.exit(0); }// Finally, call one of the methods defined above to do the work.if (directory)Compress.zipDirectory(from, to);elseCompress.gzipFile(from, to); }}
La classe java.io.FilterReader è una classe astratta che definisce un "fltro nullo" - legge i carattere da in Reader specificato e li ritorna senza modifiche. In altre parole, FilterReader definisce delle implementazioni che non fanno operazioni di tutti i metodi di Reader. Una sottoclasse deve sovrascrivere almeno i due metodi read() per eseguire il tipo di filtering che si desidera. Alcune sottoclassi possono anche sovrascrivere altri metodi se necessario. Nell'esempio RemoveHTMLReader, è una sottoclasse di FilterReader che legge testo HTML dallo stream a filtra tutti i tag HTML dal testo che ritorna.
Si poteva definire anche una classe RemoveHTMLWriter, eseguendo la stessa azione di filtraggio in una solttoclasse di java.io.FilterWriter. Per filtrare byte streams invece di character streams, avremmo utilizzato sottoclassi di java.io.FilterInputStream e java.io.FilterOutputStream.
// This example is from _Java Examples in a Nutshell_. (http://www.oreilly.com)// Copyright (c) 1997 by David Flanagan// This example is provided WITHOUT ANY WARRANTY either expressed or implied.// You may study, use, modify, and distribute it for non-commercial purposes.// For any commercial use, see http://www.davidflanagan.com/javaexamplesimportjava.io.*;/** * A simple FilterReader that strips HTML tags out of a stream of characters. * It isn't perfect: it doesn't know about <XMP> tags, for example, within * which '<' and '>' aren't to be interpreted as tags. It will also strip * '<' and '>' characters (and anything in between) out of plain text files. * For this reason, it should only be used with properly formatted HTML input. **/publicclassRemoveHTMLReaderextendsFilterReader { /** A trivial constructor. Just initialze our superclass */publicRemoveHTMLReader(Reader in) { super(in); }boolean intag =false; // Used to remember whether we are "inside" a tag /** * This is the implementation of the no-op read() method of FilterReader. * It calls in.read() to get a buffer full of characters, then strips * out the HTML tags. (in is a protected field of the superclass). **/publicintread(char[] buf,int from,int len) throwsIOException {int numchars =0; // how many characters have been read// Loop, because we might read a bunch of characters, then strip them// all out, leaving us with zero characters to return.while (numchars ==0) { numchars =in.read(buf, from, len); // Read charactersif (numchars ==-1) return-1; // Check for EOF and handle it.// Loop through the characters we read, stripping out HTML tags.// Characters not in tags are copied over any previous tags in the bufferint last = from; // Index of last non-HTML charfor(int i = from; i < from + numchars; i++) { if (!intag) { // If not in an HTML tagif (buf[i] =='<') intag =true; // check for start of a tagelse buf[last++] = buf[i]; // and copy the character }elseif (buf[i] =='>') intag =false; // Else, check for end of tag } numchars = last - from; // Figure out how many characters remain } // And if it is more than zero charactersreturn numchars; // Then return that number. } /** * This is another no-op read() method we have to implement. We * implement it in terms of the method above. Our superclass implements * the remaining read() methods in terms of these two. **/publicintread() throwsIOException { char[] buf =newchar[1];int result =read(buf,0,1);if (result ==-1) return-1;elsereturn (int)buf[0]; }}
La classe, AppRemoveHTMLReader, per l'esecuzione dell'applicazione:
importjava.io.BufferedReader;importjava.io.FileReader;/** This class defines a main() method to test the RemoveHTMLReader */publicclassAppRemoveHTMLReader { /** The test program: read a text file, strip HTML, print to console */publicstaticvoidmain(String[] args) {try {if (args.length!=1)thrownewIllegalArgumentException("Wrong number of arguments");// Create a stream to read from the file and strip tags from itBufferedReader in =newBufferedReader(new RemoveHTMLReader(new FileReader(args[0])));// Read line by line, printing lines to the consoleString line;while ((line =in.readLine()) !=null)System.out.println(line);in.close(); // Close the stream. } catch (Exception e) {System.err.println(e);System.err.println("Usage: java AppRemoveHTMLReader <filename>"); } }}
Utilizzo: java AppRemoveHTMLReader <filename>
Esempio - Filtraggio linee di testo
La classe GrepReader definisce un altro esempio di input stream custom. Questo stream legge le linee di testo da un Reader specificato e ritorna solo le linee che contengono una specificata sottostringa. In questo modo lavora come il comando fgrep di UNIX - eseguen una ricerca sull'input. GrepReader esegue il filtering, ma filtra il testo una linea alla volta, piuttosto che un carattere alla volta, per questo estende BufferedReader e non FilterReader.
// This example is from _Java Examples in a Nutshell_. (http://www.oreilly.com)// Copyright (c) 1997 by David Flanagan// This example is provided WITHOUT ANY WARRANTY either expressed or implied.// You may study, use, modify, and distribute it for non-commercial purposes.// For any commercial use, see http://www.davidflanagan.com/javaexamplesimportjava.io.*;/** * This class is a BufferedReader that filters out all lines that * do not contain the specified pattern. **/publicclassGrepReaderextendsBufferedReader {String pattern; // The string we are going to be matching. /** Pass the stream to our superclass, and remember the pattern ourself */publicGrepReader(Reader in,String pattern) { super(in);this.pattern= pattern; } /** * This is the filter: call our superclass's readLine() to get the * actual lines, but only return lines that contain the pattern. * When the superclass readLine() returns null (EOF), we return null. **/publicfinalStringreadLine() throwsIOException {String line;do { line = super.readLine(); }while ((line !=null) &&line.indexOf(pattern) ==-1);return line; }}
La classe AppGrepReader, per l'esecuzione dell'applicazione:
importjava.io.FileReader;/** * This class demonstrates the use of the GrepReader class. It prints the lines * of a file that contain a specified substring. **/publicclassAppGrepReader {publicstaticvoidmain(String args[]) {try {if (args.length!=2)thrownewIllegalArgumentException("Wrong number of arguments");GrepReader in =newGrepReader(new FileReader(args[1]), args[0]);String line;while ((line =in.readLine()) !=null)System.out.println(line);in.close(); } catch (Exception e) {System.err.println(e);System.out.println("Usage: java AppGrepReader <pattern> <file>"); } }}
Utilizzo: java AppGrepReader <pattern> <file>
Esempio - PhoneDirectoryFileDemo
Vediamo in particolare esempio PhoneDirectoryFileDemo , programma per gestire una rudimentale rubrica telefonica, che associa a un nome il numero di telefono. Utilizza una TreeMap<String, String>, per associare il nome al numero di telefono, inoltre ha funzionalità di ricerca, cancellazione, lista oltre che l'aggiunta dei record (nome, numero telefono).
Per far si che i dati immessi siano reperibili al prossimo utilizzo del programma, i dati dei record immessi sono salvati su file (in fase di startup dell'applicazione vengono letti e caricati nella TreeMap che contiene l'associazione nome e numero di telefono).