Montag, 18. Oktober 2010

Komprimierung mit C#, Teil 2 - Dateien komprimieren

Heute setzte ich das Tutorial zu Komprimierungsmethoden in C# aus dem vorigen Post fort.
Während es im selbigen um das Grundprinzip und die Komprimierung / Dekomprimierung von einfachen Zeichenketten ging, zeige ich jetzt eine etwas fortgeschrittenere Anwendung dieser Prinzipien zum Komprimieren / Dekomprimieren von ganzen Dateien.

Die Kompression:
Wie bei der Komprimierung von Zeichenketten legen wir eine neue Instanz der Klasse GZipStream an und übergeben dieser im Konstruktor einen FileStream, der auf die Zieldatei zeigt.
Wir legen weiterhin einen anderen FileStream an, der auf die Quelldatei zeigt.
Mit diesem lesen wir nun den Inhalt der alten Datei in ein byte - Array.
Schreiben wir dieses nun mit der Methode Write() des GZipStreams, schreibt dieser den komprimierten Inhalt der Quelldatei in die Zieldatei - wir haben eine Datei komprimiert, fast so, als hätten wir sie per WinZip oder einem ähnlichen Programm zu einem Archiv hinzugefügt.
Öffnet man die komprimierte Datei mit einem Entpackungsprorgamm (z.B. WinZip), kann man das ursprüngliche Bild extrahieren und öffnen.
Der entsprechende Code dazu:

        private void CompressFile(string normalFile, string compressedFile)
        {
            GZipStream CompressStream = new GZipStream(new FileStream(compressedFile, FileMode.Create), CompressionMode.Compress);
            
            FileStream NormalFileStream = new FileStream(normalFile, FileMode.Open);
            byte[] Content = new byte[NormalFileStream.Length];
            NormalFileStream.Read(Content, 0, Content.Length);
            NormalFileStream.Close();

            CompressStream.Write(Content, 0, Content.Length);
            CompressStream.Close();
        }

Nun die Dekompression:
Hierfür wird im Konstruktor des GZipStreams ein FileStream, der auf die auszulesende, komprimierte Datei zeigt, übergeben.
Außerdem wird ein neuer FileStream angelegt, welcher auf die zu schreibende, dekomprimierte Datei zeigt.
Wie im vorigen Post auch zur Dekomprimierung benutzt, wird nun eine Endlosschleife eingesetzt, um die komprimierte Datei auszulesen.
Hierfür wird die Methode Read() des GZipStreams verwendet, welche die übergebene Anzahl an Bytes einliest und dabei dekomprimiert. Das Ergebnis wird in einem Buffer gespeichert, welcher dann über den FileStream in die neue Datei geschrieben wird.
Wurde das Ende der Datei erreicht, liest die Methode Read() weniger Bytes als in den Buffer passen würden, woran das Programm das Ende erkennt.
Die komprimierte Datei wurde nun entpackt und wieder als lesbare Datei angelegt.
Der Code dazu lautet:

        private void DecompressFile(string compressedFile, string normalFile)
        {
            GZipStream DecompressStream = new GZipStream(new FileStream(compressedFile, FileMode.Open), CompressionMode.Decompress);

            FileStream NormalFileStream = new FileStream(normalFile, FileMode.Create);

            int BytesReadCount = 0;
            byte[] Buffer = new byte[4096];

            while (true)
            {
                BytesReadCount = DecompressStream.Read(Buffer, 0, Buffer.Length);
                if (BytesReadCount != 0)
                {
                    NormalFileStream.Write(Buffer, 0, BytesReadCount);
                }
                if (BytesReadCount < Buffer.Length)
                    break;
            }

            NormalFileStream.Close();
            DecompressStream.Close();

        }

Kommentare:

  1. Funktioniert bei kleinen Dateien gut so. Das Problem ist, dass du die ganze Datei in den FileStream liest. Das heisst, wenn du eine 1GB grosse Datei Komprimieren willst geht 1GB Arbeitsspeicher flöten was nicht sehr performant ist....

    GZipStream CompressStream = new GZipStream(new FileStream(compressedFile, FileMode.Create), CompressionMode.Compress);

    FileStream NormalFileStream = new FileStream(normalFile, FileMode.Open);
    byte[] Content = new byte[NormalFileStream.Length];
    byte[] buffer = new byte[4096];
    int länge = 0;
    do
    {
    länge = NormalFileStream.Read(buffer, 0, buffer.Length);
    CompressStream.Write(buffer, 0, buffer.Length);
    }
    while (länge != 0);
    NormalFileStream.Close();
    CompressStream.Close();

    das ist schon etwas performanter. Dadurch wird die Datei nur in kleinen 4096 byte Blöcken gelesen und komprimiert.

    AntwortenLöschen
    Antworten
    1. In der Tat performanter, jedoch sollte der CompressStream in seiner Write-Methode nur die tatsächlich ausgelesene Byte-Länge schreiben, sonst kann es zu doppelten Einträgen am Ende einer komprimierten Datei kommen, da der Buffer nicht geleert wird:

      CompressStream.Write(buffer, 0, länge);

      Löschen