Cílem tohoto dílu je pro mě seznámit čtenáře se základy realizace vstupně/výstupních operací v .NET frameworku. Dozvíme jaký obecný koncept pro tyto operace je v MS.NET použit a naučíme se různými způsoby pracovat se soubory.
I/O operace v rámci .NET
V prostředí MS. NET frameworku jsou všechny vstupně/výstupní operace realizovány pomocí takzvaných datových proudů, neboli streamů. Stream je abstrakce určité sekvence bytů z nějakého zařízení, souboru, paměti nebo soketu TCP/IP. V rámci .NET je takovýto datový proud představován abstraktní třídou Stream. Tato třída je bázovou třídou pro všechny datové proudy pracující s konkrétními objekty (souboru, I/O, zařízení, paměť). Třídy představující konkrétní datové proudy implementují její abstraktní členy potřebné pro uskutečnění požadovaného přenosu dat.
To pro nás programátory znamená velkou výhodu v podobě zobecnění přístupu k různým objektům, na kterých chceme provádět I/O operace, protože použití tříd je díky společnému rozhraní pořád stejné a tím pádem se vůbec nemusíme zabývat implementačními detaily související s přístupem ke konkrétnímu druhu objektu. Všechny třídy související s problematikou vstupně/výstupních operací nalezneme ve jmenném prostoru System.IO základní knihovny tříd .NET frameworku.
Na jednotlivých implementacích datových proudů se nacházejí implementace metod Read a Write, které použijeme ke čtení bytů respektive k jejich zápisu. Obou metodám je ve formě parametru předán odkaz na pole bytů, představující buffer (vyrovnávací paměť). V případě metody Write je obsah tohoto bufferu zapsán do datového proudu a v případě metody Read je tomu naopak a data z proudu jsou do bufferu ukládána.
Některé datové proudy podporují náhodný přístup. Podpora náhodného přístupu u proudu znamená, že v dané sekvenci bytů se můžeme nastavit na libovolnou pozici a ne jen číst sekvenci od začátku. To jestli konkrétní proud, který chceme použit pro přístup k objektu, náhodný přístup podporuje, zjistíme přečtením instanční vlastnost CanSeek. Pokud proud tuto vlastnost podporuje, můžeme požadovanou pozici nastavit pomocí vlastnosti Position.
Operace se soubory
Jak jsme se dozvěděli výše, tak pro přístup ke konkrétnímu objektu na kterém chceme provádět I/O operace použijeme k tomu určenou implementaci abstraktní třídy Stream. V případě přístupu k souborům použijeme třídu FileStream. Následující příkladu ukazuje jak je pomocí této třídy možné zapsat byty do určitého souboru.
/// <summary>
/// Ukazka zapisu do souboru pomoci metody Write tridy Stream
/// </summary>
public static void ZapisBytuDoSouboru()
{
FileStream lStream = null;
try
{
//vytvoreni streamu
lStream = new FileStream(@"C:\pokus.txt",FileMode.Create);
//vytvoreni bufferu bytu, ze ktereho bude zapsano
byte[] lBuffer = new byte[]{1,2,3,4};
//pokyn k zapisu
lStream.Write(lBuffer,0,lBuffer.Length);
//vyprazdneni bufferu a provedeni vsech neprovedenych
//zapisu do zarizeni (souboru)
lStream.Flush();
}
finally
{
//uzavreni streamu
lStream.Close();
}
}
V příkladu jsem vytvořil proud k souboru C:\pokus.txt, a uvedl jsem pomocí výčtového typu FileMode, že chci tento soubor vytvořit a pokud existuje, tak jej chci přepsat. Následně jsem vytvořil pole dat, které bude zapsáno (čísla 1,2,3,4). Po té jsem dal pokyn k zápisu pomocí metody Write, které jsem pomocí parametrů sdělil, že chci zapsat všechna data z bufferu a že je chci zapsat od jeho začátku. Zavolání metody Flush na instanci streamu zapříčiní vyprázdnění všech bufferů (pokud nějaké stream používá) a zapsaní všech dat do souboru. Nakonec je stream pomocí metody Close uzavřen.
Jak by se zapsaná data do tohoto souboru opět v programu načetla, demonstruje následující příklad.
/// <summary>
/// Ukazka nacteni souboru pomoci metody Read tridy Stream
/// </summary>
public static void NacteniBytuZeSouboru()
{
FileStream lStream = null;
try
{
//vytvoreni streamu
lStream = new FileStream(@"C:\pokus.txt",FileMode.Open);
//vytvoreni bufferu do ktereho budou zapsana nactena data
//ze zarizeni
byte[] lBuffer = new byte[4];
//nacteni dat ze souboru do bufferu
lStream.Read(lBuffer,0,lBuffer.Length);
//vypsani nactenych dat
for(int i = 0; i < lBuffer.Length; i++)
Console.WriteLine(lBuffer[i]);
}
finally
{
//uzavreni streamu
lStream.Close();
}
}
V příkladu je opět vytvořen stream a pomocí něj jsou data ze souboru načtena do pole bytů, které je následně vypsáno pomocí cyklu for.
Třídy BinaryWriter a BinaryReader
Jistě vás napadlo, že pokud je jediný možný způsob jak číst a zapisovat data pomocí bytů, tak práce s IO v .NET není zrovna nejpříjemnější. Samozřejmě tomu tak není a návrháři .NET frameworku vymyslely třídy BinaryReader a BinaryWriter. Tyto třídy jsou velmi užitečné, protože jsou schopny číst respektive zapisovat základní datové typu z/do sekvence bytů tedy do proudu. Takže tyto třídy samy o sobě vytvořit proud vedoucí ke konkrétnímu objektu, ale umí již existující proud využít a zpříjemnit nám s ním práci. Jak použít třídu BinaryWriter pro zápis dat do souboru ukazuje tento příklad.
/// <summary>
/// Ukazka pouziti tridy BinaryWriter pro
/// zapis dat do souboru
/// </summary>
public static void ZapisPomociBinaryWriteru()
{
FileStream lStream = null;
try
{
//vytvoreni streamu
lStream = new FileStream(@"C:\pokus.txt",FileMode.Create);
//vytvoreni BinaryWriteru, ktery bude vyuzivat
//nas proud k souboru
BinaryWriter lWriter = new BinaryWriter(lStream);
//zapsani retezce
lWriter.Write("Retezec");
//zapsani cisla int
lWriter.Write(69);
//zapsani cisla double
lWriter.Write(69.69);
lWriter.Flush();
lWriter.Close();
}
finally
{
lStream.Close();
}
}
V příkladu jsem vytvořil proud k souboru, který jsem následně předal konstruktoru třídy BinaryWriter a tím jsem zajistil, že data budou zapsána do mého proudu. Potom jsem pomocí různých přetížení metody Write pomocí BinaryWriteru zapsal nějaká data do proudu a tedy i do souboru.
Jak takto zapsaná data ze souboru získáme, pomocí třídy BinaryReader je ukázáno v tomto příkladu.
/// <summary>
/// Ukazka nacteni dat ze souboru pomoci tridy BinaryReader
/// </summary>
public static void CteniPomociBinaryReaderu()
{
FileStream lStream = null;
try
{
lStream = new FileStream(@"C:\pokus.txt",FileMode.Open);
BinaryReader lReader = new BinaryReader(lStream);
Console.WriteLine(lReader.ReadString());
Console.WriteLine(lReader.ReadInt32());
Console.WriteLine(lReader.ReadDouble());
}
finally
{
lStream.Close();
}
}
Zdrojové kódy k příkladům si můžete stáhnout zde.
V příštím dílu se dozvíme, co ještě skrývá jmenný prostor System.IO.