Komprimace a dekomprimace v .NET

Jan Holan       10.06.2011       I/O operace       13809 zobrazení

V .NET máme více způsobů jak komprimovat a dekomprimovat soubory nebo streamy. Podíváme se blíže na jednotlivé možnosti a jejich výhody a nevýhody.

G-ZIP

private void GZip()
{
    string testFileName = "TestFile.dat";
    string packageFileName = "Package.gz";
    string outFileName = "OutFile.dat";

    //Komprimace souboru
    using (var fs = File.OpenRead(testFileName))
    using (var compressStream = new System.IO.Compression.GZipStream(File.Create(packageFileName), System.IO.Compression.CompressionMode.Compress, false))
    {
        byte[] buffer = new byte[4096];
        int bytesRead = 0;
        while ((bytesRead = fs.Read(buffer, 0, buffer.Length)) > 0)
        {
            compressStream.Write(buffer, 0, bytesRead);
        }
    }


    //Dekomprimace souboru
    using (var decompressStream = new System.IO.Compression.GZipStream(File.OpenRead(packageFileName), System.IO.Compression.CompressionMode.Decompress, false))
    using (var fs = File.Create(outFileName))
    {
        byte[] buffer = new byte[4096];
        int bytesRead = 0;
        while ((bytesRead = decompressStream.Read(buffer, 0, buffer.Length)) > 0)
        {
            fs.Write(buffer, 0, bytesRead);
        }
    }
}

Uvedený příklad zapakuje soubor TestFile.dat do souboru Package.gz pomoci G-ZIP komprimace. V druhé částí kódu je soubor Package.gz opět dekomprimován do souboru OutFile.dat. Používá se na to třída GZipStream z namespace System.IO.Compression, která je dostupná již od FW 2.0. Jako její vstup je zde použit FileStream (File.Create nebo File.OpenRead), v praxi lze ale použít libovolný jiný otevřený stream. Nevýhody tohoto řešení jsou to, že třída si neumí poradit s klasickými zip soubory, a také do balíčku nelze zahrnout více souboru něž jeden.

Packaging

Od .NET v3.0 a vyšší přichází WPF jehož součást je assembly WindowsBase.dll, ta obsahuje také namespace System.IO.Packaging. V něm jsou třídy určené pro práci s Office 2007 dokumenty ve formátu Open XML (soubory s příponami docx, xlsx, pptx atd.). Protože tyto soubory jsou uložený zip komprimací, můžeme toho využít a System.IO.Packaging použít na komprimaci i jiných našich souborů.

private void Packaging()
{
    string[] testFileNames = { "TestFile1.dat", @"Folder\TestFile2.dat" };
    string packageFileName = "Package.zip";

    //Komprimace souborů
    //Create package
    Package package = Package.Open(packageFileName, FileMode.Create, FileAccess.ReadWrite);
    package.PackageProperties.Created = DateTime.Now;
    package.PackageProperties.Creator = "Packaging Sample";
    package.PackageProperties.ContentType = "Test files";
    package.PackageProperties.Identifier = Guid.NewGuid().ToString();
    package.PackageProperties.Description = "System.IO.Packaging sample Package";

    foreach (string filename in testFileNames)
    {
        //Create a package part with URI from the filename
        PackagePart part = package.CreatePart(new Uri("/" + filename.Replace('\\', '/'), UriKind.Relative), System.Net.Mime.MediaTypeNames.Application.Zip, CompressionOption.Normal);

        using (var fs = File.OpenRead(filename))
        using (var partStream = part.GetStream())
        {
            byte[] buffer = new byte[4096];
            int bytesRead = 0;
            while ((bytesRead = fs.Read(buffer, 0, buffer.Length)) != 0)
            {
                partStream.Write(buffer, 0, bytesRead);
            }
        }
    }

    package.Close();


    //Dekomprimace souborů
    package = Package.Open(packageFileName, FileMode.Open, FileAccess.Read);

    string directory = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);

    foreach (PackagePart part in package.GetParts())
    {
        if (part.ContentType.Equals("application/zip", StringComparison.OrdinalIgnoreCase))
        {
            string file = Path.Combine(directory, part.Uri.ToString().Replace('/', '\\').Substring(1));

            string outFile = file + ".out";     //Jméno dekomprimovaného souboru pro test

            string targetDir = Path.GetDirectoryName(outFile);
            if (!Directory.Exists(targetDir))
            {
                Directory.CreateDirectory(targetDir);
            }

            using (var partStream = part.GetStream())
            using (var fs = File.Create(outFile))
            {
                //Zapis souboru
                byte[] buffer = new byte[4096];
                int bytesRead = 0;
                while ((bytesRead = partStream.Read(buffer, 0, buffer.Length)) > 0)
                {
                    fs.Write(buffer, 0, bytesRead);
                }
            }
        }
    }

    package.Close();
}

Příklad nejprve zapakuje soubor TestFile1.dat a soubor TestFile2.dat v podsložce Folder do souboru Package.zip. Provádí se to pomoci objektu Package z již zmíněného namespace System.IO.Packaging, při jeho vytváření je možné určit buď přímo jméno souboru, nebo jiný stream určený pro zápis. Také lze nastavit některé vlastnosti popisující package balíček jako Creator, ContentType, Description . jednotlivé soubory pak to balíku zapisujeme pomoci objektů PackagePart, které vytváříme metodou CreatePart, jednotlivé Parts (části) jsou identifikované jedinečnou Uri.

V druhé části kódu je soubor Package.zip opět otevřen a obsažené soubory rozpakovány. Metoda GetParts nám umožní získat jednotlivé částí – soubory z balíku, aniž bychom museli znát jejich jména tj. můžeme takto rozpakovat všechny soubory, které se v package nacházejí bez znalosti jejich jmen. Pokud bychom naopak potřebovali z package rozpakovat pouze některé konkrétní soubory, můžeme použít metodu GetPart, která podle zadaného Uri vrací příslušný part např. takto:

var part = package.GetPart(new Uri("/Folder/TestFile2.dat", UriKind.Relative));

Nyní tedy již můžeme do balíčku vložit více souborů, nevýhodou ale je to, že při komprimaci nám ve vytvořeném ZIP souboru vždy jako jeho součást přibude i popisný soubor [Content_Types].xml a další pomocné soubory ve složkách package a _rels). Také takto nelze dekomprimovat klasický ZIP soubor, protože právě tento [Content_Types].xml soubor neobsahuje (metoda GetParts nehlásí chybu, ale nic nevrátí).

Tím jsme vyčerpali možnosti, které jsou  přímo v .NETu, pokud potřebujeme něco lepšího (typicky např. dekomprimovat klasické ZIP soubory), musíme šáhnou na některou .NET knihovnu třetí strany. Totéž také musíme udělat pokud hledáte podporu komprimací v Silverlight, tam totiž ani GZipStream ani Packaging nenajdete. Takových knihoven existuje více, nejznámější jsou DotNetZip a SharpZipLib, já jsme si pro příklad vybral tu druhou z nich.

SharpZipLib

Jedná se o volnou plně .NET C# assembly pro FW 1.1/2.0/4.0 nebo CF 1.0/2.0. Knihovna podporuje Zip, GZip, Tar a BZip2 komprese, dostupná ke stažení je zde . Dále navíc existuje portace této knihovny i do Silverlightu SharpZipLib.Silverlight. Staženou knihovnu ICSharpCode.SharpZipLib.dll přidáme do referencí našeho projektu, tím můžeme využívat funkcionalitu pro ZIP kompresi v namespace ICSharpCode.SharpZipLib.Zip.

private void SharpZipLib()
{
    string[] testFileNames = { "TestFile1.dat", @"Folder\TestFile2.dat", @"EmptyFolder" };
    string packageFileName = "Package.zip";

    //Komprimace souborů
    using (var zipStream = new ZipOutputStream(File.Create(packageFileName)))
    {
        zipStream.SetLevel(7);  //Set compression level 0-store only to 9-best compression (default 6)

        foreach (string filename in testFileNames)
        {
            if (File.Exists(filename))
            {
                //File
                var entry = new ZipEntry(filename);
                entry.DateTime = File.GetLastAccessTime(filename);
                zipStream.PutNextEntry(entry);

                using (var fs = File.OpenRead(filename))
                {
                    byte[] buffer = new byte[4096];
                    int bytesRead = 0;
                    while ((bytesRead = fs.Read(buffer, 0, buffer.Length)) != 0)
                    {
                        zipStream.Write(buffer, 0, bytesRead);
                    }
                }
            }
            else if (Directory.Exists(filename))
            {
                //Directory
                var entry = new ZipEntry(filename + Path.DirectorySeparatorChar);
                entry.DateTime = Directory.GetLastAccessTime(filename);
                zipStream.PutNextEntry(entry);
            }
        }
    }


    //Dekomprimace souborů
    string directory = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);

    using (var zipStream = new ZipInputStream(File.OpenRead(packageFileName)))
    {
        ZipEntry entry;
        while ((entry = zipStream.GetNextEntry()) != null)
        {
            if (entry.IsFile)
            {
                string file = Path.Combine(directory, entry.Name);

                string outFile = file + ".out";     //Jméno dekomprimovaného souboru pro test

                string targetDir = Path.GetDirectoryName(outFile);
                if (!Directory.Exists(targetDir))
                {
                    Directory.CreateDirectory(targetDir);
                }

                using (var fs = File.Create(outFile))
                {
                    byte[] buffer = new byte[4096];
                    int bytesRead = 0;
                    while ((bytesRead = zipStream.Read(buffer, 0, buffer.Length)) > 0)
                    {
                        fs.Write(buffer, 0, bytesRead);
                    }
                }
            }
            else if (entry.IsDirectory)
            {
                string targetDir = Path.Combine(directory, entry.Name);
                if (!Directory.Exists(targetDir))
                {
                    Directory.CreateDirectory(targetDir);
                }
            }
        }
    }
}

Kód příkladu používá pro zipování třídu ZipOutputStream, pro un-zip třídu ZipInputStream. Jednotlivé soubory jsou do tohoto steamu přidávány objekty ZipEntry. Obdobně jako v předešlých příkladech jsou nejprve zapakované dva soubory do Package.zip, a ten je pak rozpakován. Navíc ZIP umožňuje také uložit do souboru archívu i prázdné adresáře, to se provádí tak, že jméno při vytváření ZipEntry musí končit oddělovačem cesty ‘\’, při rozpakování nám pak ZipEntry poskytuje vlastnosti IsFile a IsDirectory. Touto knihovnou můžeme na rozdíl od Packaging rozpakovat jakýkoliv ZIP soubor.

Knihovna umožňuje ještě ZIP soubor načíst pomoci třídy ZipFile, kde lze přistoupit k jednotlivým položkám pomoci metody GetEntry podle jména souboru. Pak je ale vyžadováno, aby byl vstupní stream zipu seekovatelný. Příklady uvedené v tomto článku toto naopak nevyžadují, a lze je tedy používat nejen na soubory, ale např. na kompresy dat přenášené streamovaně WCF službou atd.

 

hodnocení článku

0       Hodnotit mohou jen registrované uživatelé.

 

Nový příspěvek

 

RE: Komprimace a dekomprimace v .NET

Nyní jsem narazil že existuje nová knihovna Microsoft.Bcl.Compression. To je tedy nyní další možný způsob jak komprimovat a dekomprimovat soubory v .NET

Více viz. http://blogs.msdn.com/b/dotnet/archive/2...

nahlásit spamnahlásit spam 0 odpovědětodpovědět

RE: Komprimace a dekomprimace v .NET

Už si to moc nepamatuju - možná se muselo explicitně volat Flush(). Ale snad je to už opravené...

nahlásit spamnahlásit spam 0 odpovědětodpovědět

RE: Komprimace a dekomprimace v .NET

Tak to ani nevím, že tam byl bug, ale čekal bych že tam už teď není, protože metoda Dispose je v base třídě Stream vnitřně implementovaná tak, že volá pouze Close() (a ta Dispose(true)). Ale každopádně díky za podnět.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

RE: Komprimace a dekomprimace v .NET

Jen bych dal bacha, že ve třídách z namespace System.IO.Compression byl (možná ještě je) bug, takže i když se použilo using, muselo se na konci using bloku explicitně zavolat Close (jinak se nezapsalo všechno).

nahlásit spamnahlásit spam 0 odpovědětodpovědět
                       
Nadpis:
Antispam: Komu se občas házejí perly?
Příspěvek bude publikován pod identitou   anonym.

Nyní zakládáte pod článkem nové diskusní vlákno.
Pokud chcete reagovat na jiný příspěvek, klikněte na tlačítko "Odpovědět" u některého diskusního příspěvku.

Nyní odpovídáte na příspěvek pod článkem. Nebo chcete raději založit nové vlákno?

 

  • Administrátoři si vyhrazují právo komentáře upravovat či mazat bez udání důvodu.
    Mazány budou zejména komentáře obsahující vulgarity nebo porušující pravidla publikování.
  • Pokud nejste zaregistrováni, Vaše IP adresa bude zveřejněna. Pokud s tímto nesouhlasíte, příspěvek neodesílejte.

přihlásit pomocí externího účtu

přihlásit pomocí jména a hesla

Uživatel:
Heslo:

zapomenuté heslo

 

založit nový uživatelský účet

zaregistrujte se

 
zavřít

Nahlásit spam

Opravdu chcete tento příspěvek nahlásit pro porušování pravidel fóra?

Nahlásit Zrušit

Chyba

zavřít

feedback