S formátem GZIP jste se nepochybně už setkali. Ostatně, pokud používáte běžný prohlížeč, i tato stránka byla při přenosu k vám zkomprimována a váš prohlížeč ji rozbalil. Účelem komprimace je snížit objem dat při jejich ukládání a přenosu. Existují v zásadě dva způsoby, jak se toho dá dosáhnout.
Ztrátové komprimační algoritmy, jejichž typickým příkladem je komprese používaná v JPEG a MP3 souborech pracuje na principu degradace přesnosti. Využívá omezených schopností lidských smyslů (zraku, sluchu) a vypouští "nadbytečné" detaily, čímž data ztrácejí na objemu. Bývají extrémně účinné, ale mají omezenou použitelnost - nejedná se ani tak o komprimaci, jako o nevratné zničení části informace.
Bezztrátové komprimační algoritmy obecně fungují na tom principu, že v datech vyhledávají opakující se sekvence a pomocí "slovníku" je nahrazují kratšími sekvencemi. Jistě znáte klasické archivy typu ZIP, RAR nebo CAB (pamětníci si možná vzpomenou ještě na ARJ a geekové na 7Zip :-). Při procesu dekomprese se data pomocí slovníku přeloží zpátky a původní informace se obnoví v celém rozsahu.
Jedním z nejznámějších komprimačních algoritmů je DEFLATE, založený na kombinaci Huffmanova kódování a LZ77. Je popsán v RFC 1951 a v prostředí .NET je implementován prostřednictvím třídy System.IO.Compression.DeflateStream
. Tento algoritmus se zpravidla nepoužívá v surovém stavu, samostatně, ale jako součást jiného formátu, který umožňuje uložit i metadata, jako například název a atributy původního souboru, kontrolní součet a jiné.
Většina uživatelů bude nepochybně znát formát ZIP, který umožňuje sbalit do archivu velké množství souborů spolu s jejich metadaty. ZIP formát interně využívá výše popsaný DEFLATE algoritmus. Podpora tohoto formátu není vestavěná přímo v .NET Frameworku, ale existuje široce používaná volně dostupná knihovna #ZipLib (SharpZipLib), která umožňuje s těmito archivy pracovat.
Velmi často používaný je také formát GZIP, definovaný v RFC 1952. Jedná se o jednoduché rozšíření nad DEFLATE algoritmem, kdy se zkomprimovaný stream doplní o původní název souboru a kontrolní součet. GZIP se používá buďto samostatně (soubory mají obvykle příponu .gz
) a nebo jako součást specifikace jiného formátu či protokolu - typickým příkladem budiž již zmiňovaná HTTP komprese. V .NET Frameworku je podpora pro GZIP formát implementována jako třída System.IO.Compression.GZipStream
.
Výhodou a současně i omezením GZIPu je, že se jedná o streamovou kompresi a že neumožňuje do jednoho archivu umístit více souborů (narozdíl od ZIPu). Ve světě Unixových systémů se to obvykle řeší tím, že se soubory sloučí do jednoho formátu programem TAR a pak teprve zkomprimují GZIPem - výsledný soubor mívá obvykle příponu .tar.gz,
případně .tgz
.
Kdy kompresi využít a kdy ne?
Kompresi je vhodné využít v případě, že máte co do činění s daty, která mají velkou režii a jsou snadno komprimovatelná. Typickým zástupcem této kategorie je většina značkovacích jazyků, tedy zejména HTML a XML. Objem složitějších dokumentů v těchto formátech lze snížit klidně řádově.
Nutnou podmínkou pro použití GZIP komprese samozřejmě je, aby ji podporovaly obě strany, pokud si máte např. vyměňovat data s jiným programem, musí kompresi podporovat i ten. Nezapomeňte také na to, že po komprimaci přestanou být data na první pohled čitelná pro člověka.
Nemá smysl komprimovat data, která už jednou zkomprimovaná jsou. To se týká i řady formátů, které používají kompresi interně, například výše zmiňované JPEG a MPEG soubory (obecně většina formátů používaných pro obrázky, zvuky a video). Týká se to ale třeba i formátů OpenDocument (OpenOffice.org) a OpenXML (Microsoft Office 2007). Oba dva tyto formáty jsou principiálně smečka XML souborů sbalených do ZIP archivu a dále je komprimovat nemá smysl.
Komprese obecně nefunguje nad náhodnými daty, resp. nad daty majícími náhodnou charakteristiku. Nemá tedy smysl komprimovat data šifrovaná, protože dobré šifrovací algoritmy dávají na výstupu data náhodné povahy. Je nicméně dobrý nápad data zkomprimovat předtím, než je zašifrujete.
Kompresi byste také neměli využívat v případě, že se potýkáte s nedostatkem výpočetní kapacity, protože komprimace a dekomprimace klade zvýšené nároky na procesor a paměť.
Komprimace a dekomprimace dat v kódu
Třídy DeflateStream
a GZipStream
jsou - jak již název napovídá - poděděné od System.IO.Stream
. Jejich konstruktor dostává jako parametr underlying stream
(typicky např. soubor, ale může to být i síťové připojení nebo cokoliv jiného) a směr, jakým se operace provádí - tj. komprese nebo dekomprese.
Potom stačí do takto vytvořené instance zapisovat (při kompresi) nebo z ní číst (při rozbalování) jako z normálního streamu. Komprimační třída provede odpovídající operace nad tím "spodním" streamem a celá operace je velmi transparentní.
Následující kód načte XML dokument ze souboru document.xml
a uloží ho zkomprimovaný do souboru document.xml.gz
.
XmlDocument doc = new XmlDocument();
doc.Load("document.xml");
using (FileStream gzFile = File.Create("document.xml.gz"))
using (GZipStream gzStream = new GZipStream(gzFile, CompressionMode.Compress)) {
doc.Save(gzStream);
}
Načtení takto zkomprimovaného souboru by bylo obdobné, pouze by stačilo obrátit směr komprimace a ze streamu číst, ne do něj zapisovat.
Jak poznat GZIP kompresi
Ideální je zařídit, aby se vašem formátu byla komprese nepovinná a aby program dokázal automaticky detekovat, zda byla či nebyla použita. GZIP formát začíná vždy trojici znaků, jejichž šestnáctková hodnota je 0x1F, 0x8B a 0x08. Stačí se tedy podívat na začátek souboru a pokud začíná tímto magic number
, jde o GZip stream.
Třída GZipXmlDocument
Následující třída rozšiřuje možnosti třídy System.Xml.XmlDocument
o práci s komprimovanými daty:
using System.IO;
using System.IO.Compression;
using System.Xml;
public class GZipXmlDocument : XmlDocument {
// Každý GZIP stream začíná následujícími třemi bajty
private static readonly byte[] GZipSignature = { 0x1f, 0x8b, 0x08 };
public override void Load(Stream inStream) {
// Načíst první tři bajty ze streamu
byte[] sig = new byte[3];
inStream.Read(sig, 0, 3);
inStream.Seek(0, SeekOrigin.Begin);
if (sig[0] == GZipSignature[0] && sig[1] == GZipSignature[1] && sig[2] == GZipSignature[2]) {
// Je to GZIP signatura - před načtením soubor dekomprimuj
using (GZipStream gzs = new GZipStream(inStream, CompressionMode.Decompress)) {
base.Load(gzs);
}
}
else {
// Není to GZIP signatura - načíst normálně
base.Load(inStream);
}
}
public override void Load(string filename) {
using (Stream inStream = File.OpenRead(filename)) this.Load(inStream);
}
public void Save(Stream outStream, bool compress) {
if (compress) {
// Je požadována komprese
using (GZipStream gzs = new GZipStream(outStream, CompressionMode.Compress)) {
base.Save(gzs);
}
}
else {
// Není požadována komprese
base.Save(outStream);
}
}
public void Save(string filename, bool compress) {
using (Stream outStream = File.Create(filename)) this.Save(outStream, true);
}
}
Třída přepisuje metodu Load
a přidává jí schopnost automaticky detekovat GZIP kompresi. Pokud data začínají odpovídajícím magic number, dekomprimují se, jinak se načtou beze změny. Dále pak přidávám dva overloady metody Save
, kdy je možno uložit dokument komprimovaně. Řeším pouze metody, které pracují s obecným streamem nebo se soubory, nikoliv metody pracující s TextReader
em, XmlReader
em a podobně.