Cosa sono gli Streams? Non sono altro che sequenze di dati che possono essere accedute (lette/scritte) sequenzialmente o in maniera random.
Vi sono vari tipi di stream e cioè:
- FileStream (System.IO),
- MemoryStream (System.IO),
- CryptoStream (System.Security),
- NetworkStream (System.Net),
- GZipStream (System.Compression),
Tutti questi tipi di stream derivano dalla classe base astratta Stream che ha le seguenti proprietà:
- CanRead/CanSeek/CanTimeout/CanWrite: flag in lettura che indicano se lo stream: supporta la lettura/supporta il posizionamento/può andare in timeout/supporta la scrittura
- Length: ritorna la lunghezza in byte dello stream,
- Position: consente di leggere/impostare la posizione corrente sullo stream. [Position =< Length]
A livello di operazioni:
- Close: chiude lo stream e libera qualsiasi risorsa associata ad esso,
- Read/ReadByte: legge un numero di byte dallo stream partendo dalla posizione corrente, aggiornando la posizione corrente,
- Write/WriteByte: scrive sullo stream partendo dalla posizione corrrente, aggiornando la posizione corrente
- Seek: imposta la posizione corrente all’interno dello Stream
- Flush: garantisce che tutti i dati presenti nei buffer in memoria sia effettivamente scritti sullo stream.
Iniziando a parlare dei FileStream, introduciamo la classe statica File la quale permette di creare e aprire i files per leggere e scrivere. Considerando le operazioni sui file, la classe File è identica a FileInfo (descritta nel precedente post). Questa classe ritorna oggetti di tipo FileStream (una classe che rappresenta un file all’interno del file system) o StreamReader e StreamWriter (classi che wrappano FileStream e consentono operazioni di lettura e scrittura sequenziali). I principali membri:
- AppendText: Apre un file (o lo crea se non esiste) e ritorna uno StreamWriter
- Create/Open: Crea/Apre un file e ritorna un FileStream,
- OpenText: apre un file e ritorna StreamReader
- ReadAllBytes/ReadAllLines/ReadAllText: apre un file, legge il contenuto in un array di bytes/array di string/string e chiude il file in una operazione atomica,
- WriteAllBytes/WriteAllLines/WriteAllText: sono la controparte dei metodi riportati sopra per la scrittura.
Analogamente alla classe File vi è la classe statica Directory che presenta una serie di metodi statici per manipolare e creare le directories, ad esempio:
- CreateDirectory/Delete: crea/cancella directory in un path specificato,
- Exists: determina se una directory esiste o meno,
- GetCurrentDirectory/SetCurrentDirectory: ritorna/imposta un oggetto di tipo DirectoryInfo relativo alla directory corrente,
- GetFiles: ritorna i nomi dei file in una directory,
- GetLogicalDrives: ritorna una lista dei drives del sistema (e.g. “c:\”)
Con i metodi di gestione dei file visti sopra, vengono usati due enumerativi:
FileAccess che specifica i diritti richiesti durante l’apertura di un file (Read, Write, ReadWrite) e FileMode che specifica come un file debba essere creato/aperto (Append, Create, CreateNew, Open, OpenOrCreate, Truncate).
Come già accennato le funzionalità base per aprire un file streams e leggere/scrivere il contenuto sono date dalla classe FileStream la quale dispone dei seguenti proprietà:
- Handle: file handle,
- Name: nome del file,
- più tutte quelle derivate dalla classe base astratta Streams e viste sopra.
e i seguenti metodi:
- Lock/Unlock: mantiene/libera un lock sullo stream in modo che nessun altro processo possa alterarlo,
- più tutti i metodi derivate dalla classe base astratta Streams.
Per facilitare la lettura e la scrittura sequenziale, il framework .NET metta a disposizione due classi StreamReader e StreamWriter.
StreamReader consente di leggere dati come stringhe da una qualsiasi classe derivata da Stream.
Proprietà:
- BaseStream: ritorna lo stream che StreamReader sta leggendo,
- CurrentEncoding: ritorna il tipo di encoding dello stream BaseStream,
- EndOfStream: indica se si è raggiunti la fine dello stream BaseStream.
Metodi:
- Close: chiude lo stream
- Peek: recupera il successivo (rispetto alla posizione corrente) carattere,
- Read/ReadBlock/ReadLine/ReadToEnd: legge i successivi caratteri per insieme/blocco/linea/sino alla fine.
Analogamente StreamWriter consente di scrivere dati di tipo stringa su una qualsiasi classe derivata da Stream.
Proprietà:
- AutoFlush: legge o imposta l’autoflush ad ogni chiamata al metodo Write.
- BaseStream: ritorna lo stream che StreamWriter sta scrivendo,
- NewLine: imposta il carattere di nuova riga,
Metodi:
- Close: chiude lo stream BaseStream
- Write/WriteLine: scrive sullo stream/scrive sullo stream includendo il carattere di NewLine
StreamReader e StreamWriter derivano rispettivamente dalle classi astratte TextReader e TextWriter le quali rappresentano l’interfaccia per leggere e scrivere dati basati su testo.
Analogamente esistono anche le classi per leggere/scrivere dati binari BinaryReader e BinaryWriter.
Tornando ai vari tipi di stream parliamo ora della classe MemoryStream la quale fornisce le funzionalità per creare degli stream in memoria.
Oltre alle proprietà della classe base abbiamo:
- Capacity: legge o setta il numero di bytes allocati per uno stream,
Come metodi:
- GetBuffer: ritorna un array di unsigned bytes usato per creare lo stream,
- ToArray: scrive l’intero stream su un array di bytes.
- WriteTo: scrive il MemoryStream su un altro stream.
I MemoryStream sono utili per lavorare con i dati in memoria e poi consolidarli ad esempio su disco. Per farlo si può usare la seguente procedura:
1) Creazione del MemoryStream,
2) Creazione del StreamWriter associato al MemoryStream,
3) Creazione di un FileStream
4) Uso del metodo WriteTo dello StreamWriter per scrivere sullo FileStream.
Un altro modo per migliorare le performance delle letture e scritture sugli stream e bufferizzarle. A questo scopo si può usare la classe BufferedStream che ingloba lo Stream da cui leggere/scrivere e interpone un buffer invece di leggere/scrivere ogni volta dallo Stream. Ad esempio supponendo di voler scrivere su un file, si può usare la seguente procedura:
1) Si crea un oggetto FileStream associato ad un nuovo file,
2) Si crea un oggetto BufferedStream specificando il FileStream,
3) Si usa uno StreamWriter per scrivere dati sul BufferedStream.
Concludiamo questa panoramica sugli stream con le classi GZipStream e DeflateStream: entrambe si occupano di comprimere o decomprimere sequenze di dati sino a 4Gb di dimensione (GZipStream usa un formato portabile dunque da preferirsi se lo stream generato viene poi inviato ad altre piattaforme).
Le proprietà/metodi di GZipStream/DeflateStream sono tutti ereditati da Stream a parte:
- BaseStream: ritorna lo Stream su cui la classe lavora,
Un’importante differenza rispetto alle altre classi è che GZipStream/DeflateStream ricevono in input uno stream e producono in output un altro stream.
Vediamo come utilizzare queste classi per comprimere:
1) Si apre in lettura lo stream del file sorgente da comprimere,
2) Si apre in scrittura il file destinazione
3) Si crea un oggetto di classe GZipStream/DeflateStream passando nel costruttore lo stream destinazione e specificando CompressionMode.Compress
4) Si leggono i dati dal file sorgente e li si inseriscono nel oggetto GZipStream/DeflateStream
La procedura per decomprimere è un po’ diversa:
1) Si crea un oggetto di classe GZipStream/DeflateStream passando nel costruttore lo stream sorgente (quello compresso) e specificando CompressionMode.Compress,
2) si apre in scrittura il file di destinazione,
3) leggendo dal oggetto GZipStream/DeflateStream si scrive sullo Stream di destinazione.
Nel prossimo post parliamo di Isolated Storage.