Technology Experience

Contenuti gestiti da Igor Damiani
posts - 949, comments - 2741, trackbacks - 15120

My Links

News

  • Questo blog si propone di raccogliere riflessioni, teoriche e pratiche, su tutto quello che riguarda il world-computing che mi sta attorno: programmazione in .NET, software attuale e futuro, notizie provenienti dal web, tecnologia in generale, open-source.

    L'idea è quella di lasciare una sorta di patrimonio personale, una raccolta di idee che un giorno potrebbe farmi sorridere, al pensiero di dov'ero e cosa stavo facendo.

    10/05/2005,
    Milano

Archives

Post Categories

Generale

[70-536, #22] La classe GZipStream per comprimere stream di dati

Per motivi di studio, e non perchè vorrò usarla praticamente (ma mai dire mai), ho provato e riprovato la classe GZipStream del FX2.0 per comprimere flussi di dati. Ho avuto diverse difficoltà, perchè ritengo che l'Intellisense un po' impreciso: l'unico vero aiuto l'ho avuto dalla pagina con un po' di codice sorgente che dimostra veramente come utilizzare i metodi di GZipStream. Questa classe, in combinata con DeflateStream, permette di comprimere e decomprimere qualsiasi array di bytes, indipendentemente dalla loro provenienza: altri files, contenuti di TextBox, interi DataSet, e così via.

Ho scritto un'utility che mostra la bontà dell'algoritmo di compressione, che ho trovato molto variabile, nel senso che ha un ratio di compressione del 90% in alcuni casi, in altri aumenta del 50% la dimensione del file originale. Beh, insomma, c'è da sapere che esiste e, se volete sapere come funziona, continuate a leggere questo post.

Introduzione
Dunque, in questo post vedremo come comprimere uno stream. In fase di creazione di un oggetto GZipStream, occorre specificare almeno due parametri di fondamentale importanza:

  1. lo stream di output su cui vogliamo scrivere i bytes compressi
    (ad esempio, un MemoryStream o un FileStream)
  2. la modalità con cui vogliamo operare, tratta dall'enum CompressionMode

Quindi, per esempio, possiamo dichiarare un oggetto GZipStream in questo modo:

FileStream file = new FileStream("C:\\Compresso.dat", System.IO.FileMode.Create);
GZipStream zip = 
new GZipStream(file, System.IO.Compression.CompressionMode.Compress);

Così facendo, qualsiasi cosa venga scritta sullo stream zip viene prima compressa, e poi scritta effettivamente. La classe GZipStream espone un metodo Write che accetta in ingresso un byte[] che rappresenta la source di dati che vogliamo comprimere e scrivere su file. Ad esempio, possiamo fare quanto segue:

FileStream readingFile = new FileStream(txtFilename.Text, FileMode.Open);
byte[] bufferInput = new byte[readingFile.Length];
readingFile.Read(bufferInput, 0, (
int)readingFile.Length);

Con le tre righe di codice qui sopra abbiamo 1) aperto uno stream per leggere un qualsiasi file, 2) inizializzato un byte[] adatto a contenere l'intero file e 3) riempito effettivamente il byte[] con lo stream. A questo punto, possiamo dare in pasto all'oggetto GZipStream l'array bufferInput. Tale array verrà compresso e scritto sullo stream di output che abbiamo creato nel primo riquadro di codice:

using (GZipStream zip = new GZipStream(compressed, CompressionMode.Compress, true))
{
    zip.Write(bufferInput, 0, bufferInput.Length);
    ratio = 100 - (((
double)compressed.Length / (double)readingFile.Length) * 100);
}

Fondamentalmente, ci sono due metodi diversi per scrivere sullo stream puntato dall'istanza di GZipStream. Il primo metodo consiste nel chiamare il metodo Write come abbiamo fatto qui sopra, che però accetta come parametro di input solo un byte[]: a noi in questo caso va più che bene, ma se avessimo qualcosa di diverso?

Esiste in questo caso un secondo metodo che consiste nello sfruttare il polimorfismo di .NET: la classe GZipStream eredita da Stream. Se un qualsiasi oggetto .NET può scrivere qualcosa su uno Stream, allora può anche scrivere su GZipStream e di conseguenza essere compresso. Mi spiego meglio. Supponiamo di avere un DataSet popolato con DataTable, DataRow e così via. Possiamo chiamare il metodo WriteXml che, guarda caso, può essere indirizzato verso uno Stream. Se questo Stream fosse uno GZipStream, l'XML prodotto verrebbe compresso in modo trasparente, senza alcuno sforzo da parte nostra:

System.Data.DataSet ds = new System.Data.DataSet("mioDataSet");
// Riempio il dataset con i miei dati
ds.WriteXml(zip);

Oppure, ancora, potremmo serializzare in modo compresso, con poche linee di codice:

FileStream file = new FileStream("C:\\Compresso.dat", System.IO.FileMode.Create);
Compression.GZipStream zip = 
new GZipStream(file, CompressionMode.Compress);
XmlSerializer ser = 
new XmlSerializer(typeof(string));
ser.Serialize(zip, "hello world!");
file.Close();
zip.Close();

Con l'esempio qui sopra, abbiamo semplicemente 1) aperto uno stream in scrittura 2) istanziato un oggetto GZipStream che vada a scrivere su questo stream di output 3) istanziato un semplice XmlSerializer per le stringhe 4) effettuato una serializzazione della stringa "hello world!" sullo stream compresso, che corrisponde in chiaro al seguente XML:

<?xml version="1.0"?>
<string>
hello world!</string>

Molto semplice, vero?  Dopo aver eseguito queste linee di codice, ci ritroviamo su HD un file C:\Compresso.dat che contiene la nostra serializzazione, ma in formato compresso (e non human-readable).

E la DeflateStream?
FX2.0 ci mette a disposizione anche la classe DeflateStream. Funziona esattamente allo stesso modo della GZipStream descritta in questo post, solo che applica un algoritmo diverso per comprimere e decomprimere. Di conseguenza, eredita anch'essa dalla classe Stream e possiamo applicare tutti i criteri e le logiche che abbiamo visto.

E la decompressione?
Lo dico onestamente: ho trovato macchinoso l'utilizzo di queste classi. Con la decompressione infatti sto ancora litigando. In breve, è sufficiente dichiarare un oggetto GZipStream, ma con il parametro CompressionMode.Decompress. Qual'è il vero problema? Lo dico velocemente, perchè il blog non è il luogo adatto per fare e porre domande: per decomprimere, devo utilizzare il metodo Read, specificando un array di bytes su cui dovrò andare a scrivere i dati decompressi. Quindi:

zip.Read(bufferOutput, 0, bufferOutput.Length);

Il vero problema con cui mi sto scontrando è che io non so a priori la dimensione da dare a bufferOutput, perchè non posso sapere a priori quanto sarà grande uno stream di bytes che compresso occupa 128KB.  Lo stesso esempio tratto da MSDN fa una cosa "strana": dichiara un array di bytes grande come quello compresso + 100: non capisco, perchè non è detto - come ho detto prima - che la compressione funzioni a dovere. Provate a far macinare alla GZipStream un file JPG e vedrete. Beh, insomma, ho ancora molti dubbi sulla questione.

powered by IMHO 1.2

Print | posted on venerdì 3 marzo 2006 19:00 | Filed Under [ Esame 70-536 ]

Feedback

Gravatar

# [70-536, #25] Teoria sulle classi Stream di .NET e un caso pratico di TripleDES

17/03/2006 14:10 | Technology Experience
Gravatar

# Re: Comprimere e decomprimere un array di byte

21/01/2007 17:30 | Around and About .NET World
Comments have been closed on this topic.

Powered by:
Powered By Subtext Powered By ASP.NET