Gestione degli Stream ( Flussi ) in Java

Per capire il concetto di stream ( flusso ) che si ritrova in tutti i linguaggi di programmazione, dobbiamo pensare ad un fiume di caratteri che ha una sorgente ( input stream ) ed una foce ( output stream ). Dal punto di vista informatico avremo quindi dei dati che possono giungere in ingresso nell’elaboratore, oppure uscire dallo stesso verso un monitor o stampante o altro dispositivo di output. Non esistono flussi di lettura / scrittura, quindi se volessimo comunicare con un dispositivo in entrambe le direzioni dovremmo creare due stream distinti, uno per la lettura ed uno per la scrittura. Solitamente lo stream di input standard è associato alla tastiera, lo stream di output è invece generalmente associato al monitor. Nelle scorse lezioni di questo corso, ogni volta che utilizzavamo la funzione println, mandavamo dei caratteri su stdout ( standard output ). Davanti al nome della funzione abbiamo sempre inserito System.out ed ora possiamo capire che è collegato all’output stream. Per toglierci la curiosità non dobbiamo fare altro che andare a controllare il codice sorgente della classe java.lang.System in cui troveremo public final static PrintStream out = … e spostandoci nel package java.io potremmo vedere la classe PrintStream, che deriva da FilterOutputStream, che a sua volta deriva dalla classe OutputStream. Possiamo dire allora che l’oggetto System.out discende proprio dalla classe OutputStream, che implementa le funzioni base che ogni output stream deve avere:

  • …write(int b)… che scrive il byte meno significativo
  • …write(byte b[])… che scrive un intero vettore di byte
  • …write(byte b[], int off, int len)… che scrive un sottoinsieme del vettore di byte
  • …flush()… che forza la scrittura dei dati rimasti in cache
  • …close()… che chiude lo stream
// file Main.java

package corso;

public class Main
{
    public static void main(String[] args)
    {
           byte[] v = {67, 73, 65, 79, 0};

           try
           {
                System.out.write(v);
                System.out.flush();
           }
           catch (Exception e) {}
    }
}

In questo semplicissimo esempio abbiamo utilizzato le funzioni base di OutputStream e stampato i caratteri equivalenti ai codici ASCII della parola “CIAO”. Oltre alla variabile System.out esiste anche la simmetrica System.in che come potete immaginare è legata allo stdin ( standard input ) ed appartiene alla classe InputStream: public final static InputStream in = … I metodi che appartengono a questa classe sono tipici dell’input:

  • …read()… che legge un byte e restituisce -1 una volta raggiunta la fine dello stream
  • …read(byte b[])… nella quale i dati letti vengono inseriti nel vettore di byte
  • …read(byte b[], int off, int len)… vengono memorizzati len byte nel vettore a partire da off
  • …skip(long n)… vengono saltati i prossimi n byte presenti nello stream
  • …available()… restituisce il numero di byte nello stream
  • …close()… chiude lo stream
  • …mark(int readlimit) consente di ricordarsi la posizione nello stream
  • …reset()…  ritorna la posizione salvata nello stream
  • …markSupported() restituisce true se lo stream supporta le operazioni di mark e reset
// file Main.java

package corso;

public class Main
{
   public static void main(String[] args)
   {
      System.out.println("Premere C + INVIO per uscire");

      try
      {
        while (System.in.read() != 'C')
          break;
      }
      catch (Exception e) { }
   }
}

Semplicissimo esempio di uso dell’input da tastiera. Il problema in cui è facile incorrere lavorando con gli stream in input è quello di dover attendere che esso contenga dei dati da leggere. In questo caso si dice che il programma è bloccato ed in attesa di ricevere dati. La stessa cosa si verifica quando si sta scrivendo su uno stream di output, finché non sarà ultimata la scrittura non sarà possibile proseguire. La soluzione a questi problemi è data dal multithreading, vediamo un esempio esplicativo:

// file Main.java

package corso;

public class Main
{
   public static void main(String[] args)
   {
      System.out.println("Premere C +
             INVIO per visualizzare il messaggio");

      try
      {
         while (System.in.read() != 'C')
            break;
      }
      catch (Exception e) { }

      System.out.println("\nMESSAGGIO:
            Ti volevo dire che Java è il MASSIMO...\n");
   }
}

Ovviamente il messaggio non verrà scritto prima di uscire dal ciclo che richiede l’input dell’utente.

// file CTastiera.java

package corso;

public class CTastiera extends Thread
{
   public void run()
   {
     try
     {
       while (System.in.read() != 'C')
          break;
     }
     catch (Exception e) { }
   }
}

// file CVideo.java

package corso;

public class CVideo extends Thread
{
       public void run()
      {
          System.out.println("\nMESSAGGIO:
               Ti volevo dire che Java è il MASSIMO...\n");
      }
}

// file Main.java

package corso;

public class Main
{
  public static void main(String[] args)
  {
     System.out.println("Premere C +
        INVIO per visualizzare il messaggio");

    CTastiera t = new CTastiera();
    CVideo v = new CVideo();

    t.start();
    v.start();
  }
}

Come potete notare da questo esempio è possibile stampare il messaggio prima dell’input dell’utente utilizzando il multithreading visto in una lezione precedente.
Oltre allo standard input ed output esiste anche stderr ( errore standard ) e quindi System.err, che di solito va utilizzato all’interno del catch delle eccezioni. Normalmente stdout ed stderr sono entrambi direzionati al video, ma lo System.err può anche essere redirezionato verso un file e questo è il modo con cui si creano i file di log.

<< Lezione Precedente – Inizio CorsoLezione Successiva >>

Informazioni su Giampaolo Rossi

Sviluppatore di software gestionale da oltre 28 anni.
Questa voce è stata pubblicata in Java e contrassegnata con . Contrassegna il permalink.