JAVA INPUT-OUTPUT

Spesso le applicazioni hanno bisogno di utilizzare dati letti da fonti esterne o di inviare date all’esterno: un flusso (o stream) è una sequenza ordinata di dati che hanno una sorgente o una destinazione. Distinguiamo due tipi di flussi:

  • Flussi di caratteri: utilizzati per l’I/O basato sul testo e, in particolare, per trasmettere caratteri Unicode a 16 bit. I dati vengono scritti e letti in pezzi da 16 bit e quindi sono compatibili col tipo char di Java. Tali flussi vengono chiamati reader e writer e vengono gestiti dalle due classi astratte Reader e Writer del package Java.io.
  • Flussi di byte: sono utilizzati per leggere e scrivere dati non testuali, dividendoli in pezzi da 8 bit. Vengono chiamati flussi di ingresso e di uscita e sono gestiti dalle classi astratte InputStream e OutputStram. Il metodo write dei flussi di byte scrive solo byte, quindi per scrivere un intero bisogna estrarne i suoi byte con String.valueOf(i).getBytes(). Gli oggetti PrintStream semplificano le cose, consentendo di scrivere qualsiasi tipo.

La classe Java.lang.System definisce tre flussi di byte standard:

  • System.out: è un oggetto del tipo PrintStream, rappresenta l’output di default del sistema.
  • System.err: è un oggetto del tipo PrintStream, rappresenta l’output degli errori di default.
  • System.in: è di tipo InputStram e in genere rappresenta la tastiera. Non può essere usato al posto di un Reader.

Per convertire flussi di byte in flussi di caratteri si possono utilizzare i seguenti due oggetti:

  • InputStreamReader(InputStream in, String charsetName)
  • OutputStreamWriter(OutputStream out, String charsetName)

Entrambi questi oggetti usano la codifica di default del sistema, ma il costruttore consente di passare una stringa che descrive la codifica che si vuole utilizzare:

  • US-ASCII: è ASCII a 7 bit.
  • ISO-8859-1: è l’alfabeto ISO Latin no.1.
  • UTF-8: è Unicode a 8 bit con ordinamento Big Endian.
  • UTF-16: è Unicode a 16 bit e in input supporta pure l’ordinamento Little Endian.
     

I FLUSSI FILTER

Questi flussi consentono di concatenare flussi per produrre dei composti maggiormente utili: delegano le azioni di input e output al flusso al quale sono collegati. Ad esempio, un filtro che converte una stringa in maiuscolo deve estendere FilterReader e deve definire il metodo read:

public int read() throws IOException{
    int c = super.read();
    if(c==-1)
        return c;
    else
        return Character.toUpperCase((Char) c);
}

I FLUSSI BUFFERED

Questi flussi memorizzano i loro dati in un buffer, in modo da evitare che ogni operazione di input e output si rivolga direttamente al flusso successivo (vengono spesso usati insieme ai flussi File). Richiedono un riferimento al flusso incapsulato e, opzionalmente, la dimensione del buffer da utilizzare. Quando si invoca read su un flusso buffered, questo lo invoca più volte sul flusso sorgente cercando di riempire il più possibile il buffer: le successive invocazioni di read prelevano i dati dal buffer finché ce ne sono. Analogamente, write sul flusso sorgente sarà chiamato solo quando il buffer è pieno.

  • BufferedInputStream(InputStream in)
  • BufferedOutputStream(OutputStream out)
  • BufferedReader(Reader in)
  • BufferedWriter(Writer out)

I flussi di caratteri presentano metodi utili aggiuntivi. BufferedReader offre il metodo readLine() che restituisce una linea di testo sottoforma di String, riconoscendo la fine della linea grazie al separatore di linea (\n, \r oppure \r\n). BufferedWriter offre invece il metodo newLine() che permette di scrivere nel flusso un separatore di linea.
 

I FLUSSI FILE

Questi flussi consentono di trattare un File come se fosse un flusso di input o di output. Sono quattro (FileInputStream, FileOutputStream, FileReader, FileWriter) e il loro costruttore richiede o una stringa contenente il nome del file, o un oggetto Java.io.File o un oggetto FileDescriptor.

java.io.Reader
      java.io.InputStreamReader
          java.io.FileReader
 
java.io.Writer
      java.io.OutputStreamWriter
           java.io.FileWriter

Se il file non esiste, i flussi di output lo creano, se esiste e si passa true al costruttore, i nuovi dati vengono appesi al file. Il metodo flush dei flussi di output garantisce che il buffer venga svuotato nel filesystem, ma non che i dati vengano inviati al disco, perché lo stesso filesystem potrebbe avere un buffer.
 

LA CLASSE FILE

Fa parte del package Java.io e astrae il concetto di file generico: anche una directory è un file, cioè un file che contiene altri file. Il costruttore principale richiede come parametro una stringa, che rappresenta il percorso del file. Un secondo costruttore accetta il percorso della directory superiore al file e il nome del file, che concatenati compongono il percorso del file. Il percorso del file è composto da parti distinte (cioè i nomi di file e directory) separate da un carattere che dipende dal sistema operativo e che può essere ottenuto dall’invocazione di File.separator.
Un’altra interfaccia utile è FilenameFilter, che permette di filtrare i file presenti in un elenco. Per implementarla basta definire l’unico metodo accept che restituisce true se il file passato per parametro fa parte dell’output filtrato.
 

ELENCO FILTRATO DEI FILE PRESENTI IN UN DIRECORY

/**
 * Filters “.txt” files
 */

public class FileFilterAll implements java.io.FilenameFilter {
    public boolean accept(File dir, String name) {
           if (name == null) return false;
           if (name.startsWith("//")) return false;
           if (name.endsWith(".txt")) return true;
           return false;
    }
}

 

/**
 * This class implements a loader for files
 */

public class Loader {
      private File directory;
 
      public List<String> load(String path) {
            directory = new File(path);
            return load(directory.list(new FileFilterAll()));
}
...

Il metodo load prende come parametro il percorso di una directory e restituisce una lista di nomi dei file in essa presenti, filtrati secondo l’oggetto FileFilterAll, che considera solo i file di testo.
 

LEGGERE IL CONTENUTO DI UN FILE

public void readFile(File file) throws IOException{
        BufferedReader lettore = new BufferedReader(new FileReader(file));
       
//per ogni riga ne analizziamo il contenuto
        while(lettore.ready()){
                  String riga = lettore.readLine();
                    ...

  • Creiamo un FileReader (a partire dal nome del file o dall’oggetto File)
  • Creiamo il BufferedReader passandogli il FileReader
  •  Usiamo questo oggetto per leggere le linee del file
     
     
     

SCRIVERE IL CONTENUTO DI UN FILE

public class MyFileWriter {
 
     
//flusso su cui scrivere
      private BufferedWriter out = null;
 
     
//costruttore
      public MyFileWriter(String fileName) throws IOException{
            //creo il flusso, cancellando il contenuto esistente del file
            out = new BufferedWriter(new FileWriter(fileName));
      }
 
      public void writeMyFile() throws IOException{
            out.write("MY NAME: FABRIZIO");
            out.newLine();
            out.write("ENJOY");
            out.newLine();
            out.flush();
      }
}