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.