3. Práce se soubory

Jako soubor se berou data, která jsou uložena na nějakém pamětovém médiu a datový proud (stream) se dá chápat jako prostředník mezi zdrojem (soubor) a cílem (náš program) v případě získávání dat a naopak v případě posílání dat.

My pro práci se soubory budeme využívat především třídy System.IO.StreamReader a System.IO.StreamWriter. Tyto třídy implementují třídy System.IO.TextReader a System.IO.TextWriter pro práci s bytovými proudy v patřičném kódování.

K tomu, abychom s těmito třídami mohli pracovat, budeme potřebovat i nějaký proud, odkud (kam) budeme číst (zapisovat). Pro práci se soubory se používá proud System.IO.FileStream, pro pamět jako úložiště dat se používá System.IO.MemoryStream.

C# kromě práce se soubory (třída File), obsahuje také třídu System.IO.Directory, která implementuje práci s adresáři. My se budeme více věnovat práci se soubory.

3.1. Textový soubor

Textový soubor pracuje se znaky tak, aby byly čitelné uživateli. Všechna zapisovaná data se tedy převedou na typ string.

3.1.1. Zápis do textového souboru

[ukázka kódu]
using System.IO;
...
FileStream fs = new FileStream("soubor.txt", FileMode.Create);
StreamWriter sw = new StreamWriter(fs);

for(int i=0; i<text.GetLength(0); i++)
sw.WriteLine(i);
...
sw.Close();

Vytvoříme soubor s názvem soubor.txt a mód souboru nastavíme na Create, takže pokud soubor s názvem soubor.txt už existuje, bude přepsán. Pro tento soubor pak vytvoříme StreamWriter, do kterého už jednoduše vkládáme hodnoty, které se nám zlíbí.

V našem příkladě do souboru zapisujeme položky pole typu string. Abychom to mohli provést, je třeba vždy volat metodu ToString().

3.1.2. Čtení z textového souboru

Čtení se provádí analogicky, tedy vytvoříme StreamReader a proud FileStream a poté už můžeme načítat a zpracovávat naše data.

[ukázka kódu]
...
FileStream fs = new FileStream("soubor.txt", FileMode.Create);
StreamReader sr = new StreamReader(fs);

while(sr != null) {
   Console.WriteLine(sr.ReadLine());
}
sr.Close();
...

3.2. Binární soubor

Když něco zapíšeme do textového souboru, a pak to chceme zpětně číst, musíme počítat s tím, že budeme načítat řetězce. Proto pro naše textové soubory budeme muset vymyslet různé konvertory do datových reprezentací pro nás vhodných.

O něco jednodušší je to s binárními soubory, jelikož do nich můžeme ukládat binární data. Tedy při čtení můžeme načíst celou hodnotu typu decimal apod. Nevýhodou ale zůstává, že do něj můžeme s použitím třídy BinaryWriter ukládat pouze atomické typy.

3.2.1. Zápis do binárního souboru

Pro zápis do binárního souboru budeme potřebovat opět proud pracující se souborem. Ten ale můžeme vytvořit i voláním metody System.IO.File.Create() a předat ho jako parametr konstruktoru pro binární zápis dat - BinaryWriter.

[ukázka kódu]
using System.IO;
...
BinaryWriter bw = new BinaryWriter(File.Create("soubor.bin", FileMode.Create));

for(int i=0; i<text.GetLength(0); i++)
   bw.Write(i);
bw.Close();
...

3.2.2. Čtení z binárního souboru

Při čtení z binárního souboru musíme mít na paměti, který datový typ se právě má číst. Pokud jsme do našeho binárního souboru ukládali například informace o zaměstnancích včetně dat narození, platu apod., kde se vyskytuje více typů (string, decimal a jiné), musíme si na pořadí typů dát pozor.

[ukázka kódu]
using System.IO;
...
BinaryReader br = new BinaryReader(File.Open("soubor.bin", FileMode.Open));
try {
   while(true)
      Console.WriteLine(br.ReadInt32());
   }
catch (EndOfStreamException) {}
finally {
   br.Close();
}

V příkladu čtení binárních dat načítáme celá čísla tak, jak jsme je do souboru zapsali. Pro přečtení celého souboru jsme použili nekonečný cyklus a hlídáme výjimku na konec vstupu.

3.3. Serializace

Jak jsem se zmínil u binárních souborů, lze pomocí třídy BinaryWriter ukládat pouze jednoduché typy. Určitě se nám ale bude hodit, pokud budeme chtít uložit celou instanci objektu. To je samozřejmě možné. Tento proces se nazývá serializace a lze jej použít pomocí třídy System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.

[znalosti]

K tomu, abychom mohli instanci nějakého objektu uložit, umístíme před deklaraci jeho třídy atribut Serializable. Mějme například třídu Osoba, jejíž instance budeme chtít ukládat.

[ukázka kódu]
[Serializable]
class Osoba {
   private string jmeno;
   private string prijmeni;
   ...
   public Osoba(sring jmeno, string prijmeni) { ... }
}

V momentě, kdy budeme chtít nějakou instanci třídy Osoba uložit, provedeme následující:

[ukázka kódu]
...
Osoba karel = new Osoba("Karel", "Polacek")

FileStream fs = new FileStream("soubor.srd", FileMode.Create);
System.Runtime.Serialization.Formatters.Binary.BinaryFormatter output = 
   new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
output.Serialize(fs, karel);
...

V tomto příkladu vytváříme nový soubor. Podobně je možno ukládat i celé kolekce, se kterými jsme předtím pracovali.