C# – Állománykezelés


A legtöbb C# alkalmazás készítésekor fontos szempont a program bemenő adatainak kényelmes megadása. Ugyancsak jogos elvárás az alkalmazás futtatásakor keletkező adatok tárolása későbbi feldolgozáshoz. Mindkét esetben több szinten is találunk megoldásokat a .NET rendszerben, melyek közül ebben a részben a System.IO névtér osztályait hívjuk segítségül.

A System.IO névtér osztályainak áttekintése

Ha ezt az osztályt szeretnénk használni, akkor először tudatnunk kell ezt a programmal. Be kell írnunk egy új sort a többi névtérhasználathoz. Az alábbi képen ezt láthatjuk.

A .NET keretrendszerben a fájlalapú be- és kiviteli (Input/Output, I/O) műveleteket biztosító alapvető osztályok helye a System.IO névtér. A névtér több típusa a fájlrendszer mappáinak és állományainak programozott kezelésére összpontosít. Mi most csak azokat nézzük meg részletesebben, melyek fontosak lesznek számunkra a középiskolai programozási ismereteinkhez. Azért soroljuk fel ezeket az osztályokat:

  • Directory, DirectoryInfo – Ezekkel az osztályokkal a számítógép könyvtárstruktúráját kezelhetjük. Míg a Directory típus funkcionalitását statikus tagok segítségével érhetjük el, addig a DirectoryInfo osztály hasonló működéséhez objektumokat kell létrehoznunk.
  • DriveInfo – A számítógép meghajtóiról szolgáltat részletes információkat.
  • File, FileInfo – Függvényekkel segítik az állományok létrehozását, másolását, törlését, mozgatását és megnyitását, továbbá segítik FileStream típusú objektumok létrehozását. A műveletek a File osztály statikus függvényein, illetve a FileInfo osztály objektumain keresztül érthetők el.
  • Path – Statikus függvényekkel segíti a fájl- és könyvtárelérési utakat tartalmazó sztringek feldolgozását.
  • BinaryReader, BinaryWriter – Lehetővé teszik egyszerű (egész, logikai, valós) és string típusú adatok bináris formában történő tárolását és visszaolvasását.
  • BufferedStream – Más adatfolyamok olvasási és írási műveleteihez ideiglenes tárolókat (puffereket) rendel.
  • FileStream – Támogatja fájlok bájtfolyamként történő elérését, feldolgozását.
  • MemoryStream – Adatfolyamot hoz létre a memóriában, és lehetővé teszi annak bájtfolyamként történő elérését.
  • Stream – Bájtfolyamok univerzális kezelésének absztrakt alaposztálya.
  • StreamWriter, StreamReader – Ezekkel az osztályokkal soros elérésű fájlokban tárolhatunk, illetve onnan visszaolvashatunk szöveges információkat.
  • StringWriter, StringReader – Sztringekben tárolt szöveges információk kezelését (írását/olvasását) segítő osztályok.
  • TextReader, TextWriter – Bájtfolyamok karakteres feldolgozását segítő absztrakt alaposztályok.

Adatfolyamok a .NET rendszerben

A .NET rendszerben a Stream osztály minden olyan osztály őse, amely bájtok tárolásával, továbbításával foglalkozik. A bájtok lehetnek fájlban (FileStream), memóriában (MemoryStream) tárolt adatok, hálózati kommunikáció elemei (NetworkStream), vagy akár egy titkosítási eljárás alanyai (CryptoStream).

Az adatok milyenségétől függetlenül a Stream alaposztály bájtonkénti írást/olvasást biztosít, illetve lehetővé teszi az aktuális bájtpozíció lekérdezését, módosítását (seeking). Amennyiben a bájtos adatelérés nem megfelelő számunkra, olvasó és író osztályok segítségével a bájtok sorozatát szövegsorként, illetve bináris adatként egyaránt értelmezhetjük.

A Stream osztály függvényei

Függvények:

  • CanRead, CanWrite, CanSeek – Megadja, hogy az adatfolyam támogatja-e az olvasás, írás és pozicionálás műveletét.
  • Close() – Bezárja az adatfolyamot, és felszabadítja a hozzá kapcsolódó erőforrásokat.
  • Flush() – Kiüríti az adatfolyamhoz tartozó ideiglenes tárolókat, amennyiben ilyenek tartoznak az adatfolyamhoz.
  • Length – Megadja az adatfolyam bájtban kifejezett hosszát.
  • Position – Megadja az aktuális adatfolyam-pozíciót (bájtsorszámot).
  • Read(), ReadByte() – Beolvas egy bájtsort (vagy egyetlen bájtot) az adatfolyamból, és a beolvasott bájtok számával módosítja az aktuális pozíciót az adatfolyamban.
  • Seek() – Beállítja az aktuális adatfolyam-pozíciót az adatfolyam elejéhez (SeekOrigin.Begin), aktuális pozíciójához (SeekOrigin.Current), illetve a végéhez (SeekOrigin.End) képest.
  • SetLength() – Beállítja az adatfolyam bájtban kifejezett hosszát.
  • Write(), WriteByte() – Egy bájtsort (vagy egyetlen bájtot) ír az adatfolyamba, és a kiírt bájtok számával módosítja az aktuális pozíciót az adatfolyamban.

Az adatfolyamok kezelésének általános lépéseit az alábbiak szerint csoportosíthatjuk:

  1. Az adatfolyam megnyitása, melynek sikeres elvégzését követően a programunkban egy objektumon keresztül érhetjük el az adatokat.
  2. Az adatok feldolgozása, amihez három művelet közül választhatunk: írás, olvasás, pozicionálás. (Amennyiben a pozíciót kiválaszthatjuk, tetszőleges (random) elérésű adatfolyamról beszélünk, míg ellenkező esetben az elérés soros, szekvenciális.)
  3. A feldolgozás végeztével az adatfolyamot le kell zárni. Bizonyos esetekben a lezárást megelőzi az ideiglenes tárolók tartalmának mentése (flush).

A FileStream osztály

Állományok bájtos adatfolyamként való kezeléséhez használhatjuk a FileStream osztály objektumait. Az objektumok létrehozásakor különböző konstruktorok közül választhatunk. A fájl elérési útvonalának megadása mellett intézkedhetünk a megnyitás módjáról, az adatok eléréséről, illetve arról, hogy a fájlhoz más program hozzáférhet-e.

FileStream(string útvonal, FileMode nyitási_mód)
FileStream(string útvonal, FileMode nyitási_mód, FileAccess elérés)
FileStream(string útvonal, FileMode nyitási_mód, FileAccess elérés, FileShare megosztás)

A konstruktorok hívásakor a FileMode, FileAccess és FileShare típusú paraméterek helyén felsorolások elemeit kell megadnunk. Tekintsük át részletesen ezeket.

Első paraméter (kötelező megadni)
A fájl neve az elérési útjával együtt. Amennyiben nem adunk meg teljes elérési utat, akkor automatikusan a saját könyvtárában fog keresni a program. Az elérési út megadásánál dupla backslash jeleket kell használnunk, mert az úgynevezett escape karaktert nem lehet önmagában használni! Egy másik lehetőség, hogy az „at” jelet (@) használjuk az elérési út előtt, ekkor nincs szükség dupla karakterekre, mivel mindent normális karakterként fog értelmezni.

Második paraméter (kötelező megadni)
A megnyitás módja (FileMode), különböző értékeket vehet fel:

  • Create – létrehoz egy új fájlt, ha létezik, akkor a tartalmát kitörli.
  • CreateNew – létrehoz egy új fájlt, ha létezik, kivételt dob.
  • Open – megnyitja a fájlt, ha nem létezik, kivételt dob.
  • OpenOrCreate – megnyitja a fájlt, ha nem létezik, akkor létrehozza a fájlt.
  • Append – megnyit egy fájlt és automatikusan a végére pozícionál. Ha nem létezik, akkor létrehozza.
  • Truncate – megnyit egy létező fájlt, és törli a tartalmát. Ebben a módban a fájl tartalmát nem lehet olvasni. Egyébként kivételt dob.

Harmadik paraméter (nem kötelező megadni)
A hozzáférést szabályozza (FileAccess). Beállíthatjuk, hogy pontosan mit akarunk csinálni az állománnyal:

  • Read – olvasásra nyitja meg
  • Write – írásra nyitja meg
  • ReadWrite – olvasásra és írásra nyitja meg.

Negyedik paraméter (nem kötelező megadni)
Megadhatjuk, hogy más folyamatok hogyan férhetnek hozzá a fájlhoz (FileShare):

  • None – más folyamat nem férhet hozzá a fájlhoz, amíg be nem zárjuk.
  • Read – más folyamat olvashatja a fájlt.
  • Write – más folyamat írhatja a fájlt.
  • ReadWrite – más folyamat olvashatja és írhatja is a fájlt.
  • Delete – más folyamat törölhet a fájlból, de magát a fájlt nem törölheti.

A StreamWriter, StreamReader osztályok

A StreamWriter és a StreamReader osztályok minden olyan esetben kényelmesen alkalmazhatók, amikor szöveges adatokat, adatsorokat kívánunk fájlba írni, illetve onnan beolvasni. Mindkét osztály alaphelyzetben Unicode kódolású karaktereket dolgoz fel, azonban a konstruktorukban másféle kódolást is megadhatunk a System.Text.Encoding osztály statikus tulajdonságait (ASCII, Unicode, UTF8 stb.) használva.

Amennyiben a A StreamWriter és a StreamReader osztályok konstruktoraiban egy állomány elérési útját adjuk meg, az adatfolyam objektum is létre jön a konstruálás során. Stream típusú objektumot átadva a konstruktoroknak ez a lépés elmarad.

A műveletek végeztével a Close() függvénnyel kell zárni az objektumokat, és a hozzájuk tatozó adatfolyamokat. Írás objektum esetén a lezárást ajánlott a Flush() függvény hívása után elvégezni.

Az írási és az olvasási műveletek elvégzésre egy sor függvény áll a rendelkezésünkre, melyek közül csak a legfontosabbak ismertetésére térünk ki. Szövegek kiírását a StreamWriter típusú objektum függvényeinek segítségével végezhetjük el. A WriteLine() függvények a Write() függvényekkel ellentétben a szöveg kiírása után a sorvége karaktereket is kiírják – vagyis új sort kezdenek. Mindkét függvényt hasonlóan használhatjuk tetszőleges alaptípusú adat, karaktertömb, egy vagy több adat formázott kiírására.

Most nézzünk egy példát az előzőekben leírtakra. Először két darab szöveges fájlt írunk egy megadott helyre szoveg1.txt, illetve szoveg2.txt néven. A szoveg1.txt fájlban egy sort írunk, míg a szöveg2.txt fájlba két sor kerül (a \n escape karakter segítségével). Utána beolvassuk a kiírt fájlokat, és képernyőre írjuk. Itt egy egyszerű megoldást alkalmazunk a File.ReadAllLines( ) függvény segítségével, mely egy string típusú tömbbe (sorok1 ill. sorok2) olvassa be a a szöveges fájl teljes tartalmát soronként. A képernyőre írásnál a második szöveget szintén a \n escape karakter segítségével tudjuk több sorba íratni.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;

namespace allomany_kezeles
{
    class Program
    {
        static void Main(string[] args)
        {
            StreamWriter sw1 = new StreamWriter(@"d:\Programozás - gyakorló feladatok\szoveg1.txt");
            sw1.WriteLine("Digitális kultúra");
            sw1.Close();

            StreamWriter sw2 = new StreamWriter(@"d:\Programozás - gyakorló feladatok\szoveg2.txt");
            sw2.Write("Hardver ismeretek\nSzoftver ismeretek");
            sw2.Close();

            string[] sorok1 = File.ReadAllLines(@"d:\Programozás - gyakorló feladatok\szoveg1.txt");
            foreach (var elem in sorok1)
            {
                Console.Write(elem);
            }

            Console.WriteLine();
            string[] sorok2 = File.ReadAllLines(@"d:\Programozás - gyakorló feladatok\szoveg2.txt");
            foreach (var elem in sorok2)
            {
                Console.Write("\n{0}", elem);
            }

            Console.ReadKey();
        }
    }
}

A lenti példában egy másik megoldást láthatunk a fájlból való beolvasásra (a fájlba írás ugyanúgy történik, mint a fenti példában). Először létrehoztunk egy üres stringet, majd egy while ciklussal végignéztük a stream-et, és a tartalmát elhelyeztük a string típusú változóba. Ennél a megoldásnál nem tudunk több sorba képernyőre íratni.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;

namespace allomany_kezeles
{
    class Program
    {
        static void Main(string[] args)
        {
            StreamWriter sw1 = new StreamWriter(@"d:\Programozás - gyakorló feladatok\szoveg1.txt");
            sw1.WriteLine("Digitális kultúra");
            sw1.Close();

            StreamWriter sw2 = new StreamWriter(@"d:\Programozás - gyakorló feladatok\szoveg2.txt");
            sw2.WriteLine("Hardver ismeretek Szoftver ismeretek");
            sw2.Close();

            StreamReader sr1 = new StreamReader(@"d:\Programozás - gyakorló feladatok\szoveg1.txt");
            string s1 = "";
            while (!sr1.EndOfStream) // amíg el nem érjük a fájl végét
            {
                s1 = sr1.ReadLine();
            }
            Console.Write(s1);
            sr1.Close();

            Console.WriteLine();
            StreamReader sr2 = new StreamReader(@"d:\Programozás - gyakorló feladatok\szoveg2.txt");
            string s2 = "";
            while (!sr2.EndOfStream) // amíg el nem érjük a fájl végét
            {
                s2 = sr2.ReadLine();
            }
            Console.Write(s2);
            sr2.Close();

            Console.ReadKey();
        }
    }
}