1. Flussi di dati

Raramente un programma esiste come un'isola a sé stante. I programmi di solito interagiscono in qualche modo con il "mondo esterno". Ciò può avvenire attraverso la lettura di dati dalla tastiera, l'invio di messaggi, il download di pagine da Internet o, al contrario, il caricamento di file su un server remoto.

Possiamo riferirci a tutti questi comportamenti in una parola: scambio di dati tra il programma e il mondo esterno. Aspetta, non è solo una parola.

Naturalmente, lo scambio di dati stesso può essere suddiviso in due parti: ricevere dati e inviare dati. Ad esempio, leggi i dati dalla tastiera usando un Scanneroggetto: questo sta ricevendo dati. E visualizzi i dati sullo schermo usando un System.out.println()comando: questo sta inviando dati.

Nella programmazione, il termine "stream" viene utilizzato per descrivere lo scambio di dati. Da dove viene quel termine?

Nella vita reale, puoi avere un flusso d'acqua o un flusso di coscienza. Nella programmazione, abbiamo flussi di dati .

Gli stream sono uno strumento versatile. Consentono al programma di ricevere dati da qualsiasi luogo (flussi di input) e inviare dati ovunque (flussi di output). Quindi, ci sono due tipi:

  • Un flusso di input serve per ricevere dati
  • Un flusso di output serve per l'invio di dati

Per rendere i flussi "tangibili", i creatori di Java hanno scritto due classi: InputStreame OutputStream.

La InputStreamclasse ha un read()metodo che ti consente di leggere i dati da essa. E la OutputStreamclasse ha un write()metodo che ti consente di scrivere dati su di essa. Hanno anche altri metodi, ma ne parleremo più avanti.

Flussi di byte

Di che tipo di dati stiamo parlando? Che formato prende? In altre parole, quali tipi di dati supportano queste classi?

Queste sono classi generiche, quindi supportano il tipo di dati più comune: il byte. An OutputStreampuò scrivere byte (e array di byte) e un InputStreamoggetto può leggere byte (o array di byte). Ecco fatto: non supportano altri tipi di dati.

Di conseguenza, questi flussi sono anche chiamati flussi di byte .

Una caratteristica degli stream è che i loro dati possono essere letti (o scritti) solo in sequenza. Non puoi leggere i dati nel mezzo di un flusso senza leggere tutti i dati che lo precedono.

Ecco come funziona la lettura dei dati dalla tastiera attraverso la Scannerclasse: leggi i dati dalla tastiera in sequenza, riga per riga. Leggiamo una riga, poi la riga successiva, poi la riga successiva e così via. Opportunamente, il metodo per leggere le righe è chiamato nextLine().

Anche la scrittura dei dati su un OutputStreamavviene in sequenza. Un buon esempio di ciò è l'output della console. Emetti una riga, seguita da un'altra e un'altra ancora. Questo è un output sequenziale. Non puoi emettere la prima riga, poi la decima e poi la seconda. Tutti i dati vengono scritti in un flusso di output solo in sequenza.

Flussi di caratteri

Di recente hai appreso che le stringhe sono il secondo tipo di dati più popolare, e in effetti lo sono. Molte informazioni vengono trasmesse sotto forma di caratteri e intere stringhe. Un computer eccelle nell'inviare e ricevere tutto come byte, ma gli umani non sono così perfetti.

Tenendo conto di questo fatto, i programmatori Java hanno scritto altre due classi: Readere Writer. La Readerclasse è analoga alla InputStreamclasse, ma il suo read()metodo non legge byte, ma caratteri ( char). La Writerclasse corrisponde alla OutputStreamclasse. E proprio come la Readerclasse, funziona con i caratteri ( char), non con i byte.

Se confrontiamo queste quattro classi, otteniamo la seguente immagine:

Byte (byte) Caratteri (carattere)
Lettura dei dati
InputStream
Reader
Scrivere dati
OutputStream
Writer

Applicazione pratica

Le classi InputStream, OutputStream, Readere Writernon sono utilizzate direttamente da nessuno, poiché non sono associate ad alcun oggetto concreto da cui i dati possono essere letti (o in cui i dati possono essere scritti). Ma queste quattro classi hanno molte classi discendenti che possono fare molto.


2. InputStreamclasse

La InputStreamclasse è interessante perché è la classe madre di centinaia di classi discendenti. Non ha dati propri, ma ha metodi che ereditano tutte le sue classi derivate.

In generale, è raro che gli oggetti stream memorizzino i dati internamente. Uno stream è uno strumento per la lettura/scrittura di dati, ma non per l'archiviazione. Detto questo, ci sono eccezioni.

Metodi della InputStreamclasse e di tutte le sue classi discendenti:

Metodi Descrizione
int read()
Legge un byte dal flusso
int read(byte[] buffer)
Legge una matrice di byte dal flusso
byte[] readAllBytes()
Legge tutti i byte dal flusso
long skip(long n)
Salta ni byte nel flusso (li legge e li scarta)
int available()
Controlla quanti byte sono rimasti nel flusso
void close()
Chiude il flusso

Esaminiamo brevemente questi metodi:

read()metodo

Il read()metodo legge un byte dal flusso e lo restituisce. Potresti essere confuso dal inttipo restituito. Questo tipo è stato scelto perché intè il tipo intero standard. I primi tre byte di intsaranno zero.

read(byte[] buffer)metodo

Questa è la seconda variante del read()metodo. Ti consente di leggere un array di byte da un InputStreamtutto in una volta. L'array che memorizzerà i byte deve essere passato come argomento. Il metodo restituisce un numero, il numero di byte effettivamente letti.

Diciamo che hai un buffer di 10 kilobyte e stai leggendo i dati da un file usando la FileInputStreamclasse. Se il file contiene solo 2 kilobyte, tutti i dati verranno caricati nell'array del buffer e il metodo restituirà il numero 2048 (2 kilobyte).

readAllBytes()metodo

Un ottimo metodo. Legge solo tutti i dati da InputStreamfino a quando non si esaurisce e li restituisce come un array a byte singolo. Questo è molto utile per leggere file di piccole dimensioni. I file di grandi dimensioni potrebbero non adattarsi fisicamente alla memoria e il metodo genererà un'eccezione.

skip(long n)metodo

Questo metodo consente di saltare i primi n byte dall'oggetto InputStream. Poiché i dati vengono letti rigorosamente in sequenza, questo metodo legge semplicemente i primi n byte dal flusso e li scarta.

Restituisce il numero di byte che sono stati effettivamente saltati (nel caso in cui il flusso sia terminato prima che ni byte venissero saltati).

int available()metodo

Il metodo restituisce il numero di byte che sono ancora rimasti nel flusso

void close()metodo

Il close()metodo chiude il flusso di dati e rilascia le risorse esterne ad esso associate. Una volta chiuso un flusso, non è più possibile leggere altri dati da esso.

Scriviamo un programma di esempio che copia un file molto grande. Non possiamo usare il readAllBytes()metodo per leggere l'intero file in memoria. Esempio:

Codice Nota
String src = "c:\\projects\\log.txt";
String dest = "c:\\projects\\copy.txt";

try(FileInputStream input = new FileInputStream(src);
FileOutputStream output = new FileOutputStream(dest))
{
   byte[] buffer = new byte[65536]; // 64Kb
   while (input.available() > 0)
   {
      int real = input.read(buffer);
      output.write(buffer, 0, real);
   }
}



InputStreamper la lettura dal file
OutputStreamper la scrittura nel file

Buffer in cui leggeremo i dati
Finché ci sono dati nel flusso

Leggere i dati nel buffer
Scrivere i dati dal buffer nel secondo flusso

In questo esempio, abbiamo utilizzato due classi: FileInputStreamè un discendente di InputStreamper la lettura dei dati da un file ed FileOutputStreamè un discendente di OutputStreamper la scrittura dei dati in un file. Parleremo della seconda classe un po 'più tardi.

Un altro punto interessante qui è la realvariabile. Quando l'ultimo blocco di dati viene letto da un file, potrebbe facilmente contenere meno di 64 KB di dati. Di conseguenza, non dobbiamo emettere l'intero buffer, ma solo una parte di esso: i primi realbyte. Questo è esattamente ciò che accade nel write()metodo.



3. Readerclasse

La Readerclasse è un analogo completo della InputStreamclasse. L'unica differenza è che funziona con i caratteri ( char), non con i byte. Proprio come la InputStreamclasse, la Readerclasse non viene utilizzata da nessuna parte da sola: è la classe genitore per centinaia di classi discendenti e definisce metodi comuni per tutte.

Metodi della Readerclasse (e di tutte le sue classi discendenti):

Metodi Descrizione
int read()
Legge uno chardal flusso
int read(char[] buffer)
Legge una charmatrice dal flusso
long skip(long n)
Salta n charsnello stream (legge e scarta)
boolean ready()
Controlla se c'è ancora qualcosa nello stream
void close()
Chiude il flusso

I metodi sono molto simili a quelli della InputStreamclasse, anche se ci sono lievi differenze.

int read()metodo

Questo metodo ne legge uno chardal flusso e lo restituisce. Il chartipo viene ampliato in un int, ma i primi due byte del risultato sono sempre zero.

int read(char[] buffer)metodo

Questa è la seconda variante del read()metodo. Ti consente di leggere un array di caratteri da un Readertutto in una volta. L'array che memorizzerà i caratteri deve essere passato come argomento. Il metodo restituisce un numero, il numero di caratteri effettivamente letti.

skip(long n)metodo

Questo metodo consente di saltare i primi n caratteri dell'oggetto Reader. Funziona esattamente come il metodo analogo della InputStreamclasse. Restituisce il numero di caratteri effettivamente ignorati.

boolean ready()metodo

Restituisce truese sono presenti byte non letti nel flusso.

void close()metodo

Il close()metodo chiude il flusso di dati e rilascia le risorse esterne ad esso associate. Una volta chiuso un flusso, non è più possibile leggere altri dati da esso.

Per confronto, scriviamo un programma che copia un file di testo:

Codice Nota
String src = "c:\\projects\\log.txt";
String dest = "c:\\projects\\copy.txt";

try(FileReader reader = new FileReader(src);
FileWriter writer = new FileWriter(dest))
{
   char[] buffer = new char[65536]; // 128Kb
   while (reader.ready())
   {
      int real = reader.read(buffer);
      writer.write(buffer, 0, real);
   }
}



Readerper la lettura da un file
Writerper la scrittura in un file

Buffer in cui leggeremo i dati
Finché ci sono dati nello stream

Leggere i dati in un buffer
Scrivere i dati dal buffer nel secondo stream