4.3. Fájlkezelés
Ha figyelmesen végignézzük az eddig megírt programokat, akkor észrevehetünk egy nagyon lényeges, közös tulajdonságot: a program futása után, de legkésőbb a számítógép kikapcsolásakor minden adat, érték, tevékenység, amelyet a program előállított vagy elvégzett, megszűnik létezni. Ami történt, az csak a memóriában jött létre. Ha nem nyomtatjuk ki az eredményt, vagy nem tároljuk háttértárolón, akkor nincs maradandó nyoma annak, amit csináltunk.
Ezért sok esetben szükség lehet és van is, az előállított adatok tárolására háttértárolón. Ezt az állománykezelő (fájlkezelő) utasítások teszik lehetővé.
C#-ban ehhez az IO (Input/Output) osztályokra van szükségünk, amelyeket a using System.IO; segítségével érhetünk el.
Egy szöveg típusú változóban tároljuk a háttértárolón megjelenő fizikai fájl nevét. A létrehozott fájlt a megadott néven találjuk meg a háttértárolón. Ha nem adunk meg elérési utat, akkor azt a Project könyvtárban az aktuális program bin/debug könyvtárában találjuk meg.
Például:
A fizikai fájl nevét azonban nem fogjuk használni a programban, helyette egy logikai fájlnévvel fogunk dolgozni. Ehhez először létrehozzuk az üres logikai fájlt:
A két fájlt még össze kell rendelni:
4.3.1. Hibakezelés
Mielőtt megírnánk az első fájlkezelő programot, meg kell említeni a C# hibakezelő (kivételkezelő) eszközrendszerét. A nyelv ennek segítségével a program futás közbeni hibáit szűri ki. A kivételkezelő eljárásokkal tehát a futás közben megjelenő hibaüzeneteket kerüljük el, így védve a programunkat a váratlan "lefagyástól", leállástól.
Az I/O műveletek során is szükség van ilyen kivételkezelő használatára, mivel előfordulhat olyan hardverhiba, ami miatt nem tudunk a háttértárolóra írni, vagy onnan olvasni.
A C#-ban a try utasítással a program megfelelő részeihez hibakezelő eljárásokat írhatunk, amelyek megakadályozzák a program leállását. A catch paranccsal pedig "elkapjuk" azokat a kivételeket, hibákat, amelyek a try programrészben jöttek létre. Itt adhatjuk meg, hogy milyen hibakezelő eljárások fussanak le, és hogyan kezeljék a fellépő hibákat.
40. feladat
Állítsunk elő véletlenszerűen 5 egész számot a [0,100]-ból, majd írjuk ki a számokat a képernyőre és a háttértárolóra a program BIN/DEBUG könyvtárába!
Név: Fájlbaír
Megoldás:
using System;
using System.IO; // A fájlkezeléshez feltétlenül szükséges!
namespace Fajlbair
{
class Program
{
static void Main(string[] args)
{
string fajlNev = "szamok.txt";
int[] a = new int[5];
StreamWriter ir = null;
Random vsz = new Random();
Console.WriteLine(" A számok:");
for (int i = 0; i < 5; i++)
{
a[i] = vsz.Next(101);
Console.Write("{0,5}", a[i]);
}
try
{
ir = new StreamWriter(fajlNev);
for (int i = 0; i < 5; i++)
{
ir.Write("{0,4}", a[i]);
}
ir.Close(); // Ne felejtsük el lezárni a fájlt!
}
catch (IOException)
{
Console.WriteLine(" I/O hiba történt a fájl írásakor!");
}
Console.ReadKey();
}
}
}
Az ir = new StreamWriter(fajlNev); sorban egymáshoz rendeljük a fizikai és logikai fájlokat. Az alatta lévő sorban pedig egy for ciklussal az ir.Write("{0,4}", a[i]); segítségével kiírjuk a szamok.txt-be a számokat egy sorba, négy helyet lefoglalva egy számnak. Az ir.Close(); pedig bezárja az ir logikai fájlt.
Mivel a háttértárra írás a try blokkban van, ezért ha a kiírás során hiba lépne fel, akkor a catch (IOException) segítségével a hibát kezeljük és kiírjuk a képernyőre a hiba jellegét.
A fájlba írás után nézzük meg a fájlból olvasást. Szinte mindent úgy kell csinálni, mint az írás során, de értelemszerűen a Write vagy WriteLine helyett a Read vagy ReadLine utasítást használjuk, ill. a StreamWriter helyett a StreamReader segítségével hozzuk létre a logikai fájlunkat.
41. feladat
Hozzunk létre egy szövegfájlt az alábbi adatokkal, és mentsük a programunk BIN/DEBUG könyvtárába fajl.txt néven!
(Barna Barbara, Kiss István, Nagy Róbert, Pót Csaba, Nagy Evelin, Kovács Virág)
Írjunk programot, amely beolvassa az adatokat egy 6 elemű szövegtömbbe soronként és kiírja a képernyőre!
Név: Beolvas
Megoldás:
using System;
using System.IO;
namespace Beolvas
{
class Program
{
static void Main(string[] args)
{
StreamReader olvas = null;
string[] nev = new string[6];
Console.WriteLine(" Nevek listája:");
try
{
olvas = new StreamReader("fajl.txt");
for (int i = 0; i < 6; i++)
{
nev[i] = olvas.ReadLine();
Console.WriteLine(nev[i]);
}
olvas.Close();
}
catch (IOException)
{
Console.WriteLine(" I/O hiba, a fájl valószínűleg nem található!");
}
Console.ReadKey();
}
}
}
Az olvas logikai fájlt összerendeljük a fajl.txt fizikaimal, majd a ciklusban beolvassuk a nev tömbbe az adatokat soronként, és kiírjuk a képernyőre. A beolvasása után lezárjuk a fájlt a Close utasítással.
42. feladat
Kérjünk be 6 nevet és a hozzátartozó átlagot, majd írjuk ki a háttértárolóra a programunk BIN/DEBUG könyvtárába nevatlag.txt néven! A nevek két részből álljanak, az átlagok 2 tizedes pontossággal legyenek megadva! A név és a hozzátartozó átlag egy sorban legyen! A sorokat tömbben tároljuk!
Név: Névátlag
Megoldás:
using System;
using System.IO;
namespace Nevatlag
{
class Program
{
static void Main(string[] args)
{
string fajlNev = "nevatlag.txt";
string nev = "";
string[] a = new string[6];
double atlag = 0;
StreamWriter kiir = null;
for (int i = 0; i < 6; i++)
{
Console.Write(" Kérem a nevet: ");
nev = Console.ReadLine();
Console.Write(" Kérem az átlagot: ");
atlag = Convert.ToDouble(Console.ReadLine());
a[i] = nev + " " + atlag;
}
try
{
kiir = new StreamWriter(fajlNev);
for (int i = 0; i < 6; i++)
{
kiir.WriteLine(a[i]);
}
kiir.Close();
Console.WriteLine(" Sikeres fájlba írás!");
}
catch (IOException)
{
Console.WriteLine(" I/O hiba!");
}
Console.ReadKey();
}
}
}
Az adatbekérés során az aktuális nevet és átlagot egy szóközzel választottuk el, kihasználva a szöveg típusú változóknál alkalmazható + operátort. Ezt követően már csak ki kellett írni az a[i]-t, mint szöveget egy for ciklussal a már ismert módon.
43. feladat
Másoljuk az előző feladatban létrehozott nevatlag.txt fájlt a programunk BIN/DEBUG könyvtárába (ha eddig nem ott lett volna)! Olvassuk be az adatokat, és írjuk ki a képernyőre! Számoljuk ki az átlagok átlagát és írjuk ki a képernyőre a következő szöveg után: "A csoport átlaga:"
Név: Nevatlagbeolvas
Megoldás:
using System;
using System.IO;
namespace Nevatlagbeolvas
{
class Program
{
static void Main(string[] args)
{
StreamReader olvas = null;
string[] nev = new string[6];
string atlagString = "", szoveg = "";
int db = 0;
double atlag = 0, osszeg = 0;
Console.WriteLine(" Név és Átlag:");
try
{
olvas = new StreamReader("nevatlag.txt");
for (int i = 0; i < 6; i++)
{
nev[i] = olvas.ReadLine();
Console.WriteLine(nev[i]);
}
olvas.Close();
}
catch (IOException)
{
Console.WriteLine(" I/O hiba!");
}
// Átlagok kinyerése és összegzése
for (int i = 0; i < 6; i++)
{
szoveg = nev[i];
// Mivel a formátum: Vezetéknév Keresztnév Átlag (pl. "Kiss István 4,5")
// A második szóköz után következik az átlag.
for (int j = 0; j < szoveg.Length; j++)
{
if (szoveg[j] == ' ') db++;
if (db == 2 && szoveg[j] != ' ')
{
atlagString = atlagString + Convert.ToString(szoveg[j]);
}
}
atlag = Convert.ToDouble(atlagString);
osszeg = osszeg + atlag;
db = 0;
atlagString = "";
}
Console.WriteLine(" A csoport átlaga: {0:0.00}", osszeg / 6);
Console.ReadKey();
}
}
}
A nev egy szövegtömb típusú változó, ebbe olvassuk be a háttértárolóról a sorokat. Minden sorban van azonban egy vezetéknév, egy keresztnév és egy átlag, amelyeket szóköz választ el egymástól. Ezért a beolvasás után egy külső ciklusban soronként megvizsgáljuk a szöveget és megkeressük a belső j ciklussal a szóközöket.
Erre azért van szükség, mert a szöveg típusú változóból, azaz a sorból a második szóköz után szeretnénk leválasztani az átlagokat, és számmá konvertálni, mert csak így számolhatunk később átlagot. Ezt minden sorban elvégezzük és képezzük az összeget, majd nullázzuk a szóközök darabszámát (db) és az átlagot, mint szöveget (atlagString).
A fájlok írása, olvasása mellett gyakori probléma a már létező fájlok bővítése. Ezt a FileStream utasítás után adhatjuk meg. Ekkor a zárójelben nem csak a fájl nevét (és elérési útvonalát) adjuk meg, hanem azt is, hogy mit szeretnénk az állománnyal. A fájlnévtől vesszővel elválasztva a FileMode.Append paranccsal hozzáfűzhetünk a már meglévő állományunkhoz újabb adatokat. Ekkor a fájlba újabb adatokat írhatunk a meglévők után, mivel a rendszer automatikusan az Appenddel megnyitott fájl végére áll.
44. feladat
Másoljuk a 41-es feladatban létrehozott nevatlag.txt fájlt a programunk BIN/DEBUG könyvtárába! A meglévő adatokhoz írjunk még újabbakat! Az adatbevitel ENTER végjelig tartson!
Név: Hozzafuz
Megoldás:
using System;
using System.IO;
namespace Hozzafuz
{
class Program
{
static public void Main()
{
// Fájl megnyitása hozzáfűzés (Append) módban
FileStream adatok = new FileStream("nevatlag.txt", FileMode.Append);
StreamWriter iras = new StreamWriter(adatok);
string nev = " "; // Kezdőértéknek szóközt adunk, hogy belépjen a ciklusba
string[] a = new string[100];
double atlag = 0;
int i = 0;
try
{
while (nev != "")
{
Console.Write(" Kérem a nevet (Üres enter = kilépés): ");
nev = Console.ReadLine();
if (nev != "")
{
Console.Write(" Kérem az átlagot: ");
atlag = Convert.ToDouble(Console.ReadLine());
a[i] = nev + " " + atlag;
iras.WriteLine(a[i]);
i++;
}
}
iras.Close();
Console.WriteLine(" Az adatokat kiírtam a háttértárolóra!");
}
catch (IOException)
{
Console.WriteLine(" I/O hiba történt!");
}
finally
{
// Biztosítjuk, hogy a fájlok mindenképp lezáruljanak
iras.Close();
adatok.Close();
}
Console.ReadKey();
}
}
}
Fontos megemlíteni, hogy a nev változó deklarációjánál az eddigiektől eltérően nem a "semmit" adtuk meg kezdőértéknek, hanem egy szóközt. Erre azért volt szükség, mert az elöltesztelő ciklusba csak akkor tudunk belépni, ha a nev értéke nem "semmi". Amíg a nev valamilyen értéket kap, addig a ciklusban marad a vezérlés és bekérjük a nevet és az átlagot. Ha az adatbevitelkor a név megadásánál csak entert ütünk, akkor a nev értéke nullérték ("") lesz, tehát befejeztük az adatbevitelt, akkor az átlagot már nem kell bekérni, a ciklust elhagyhatjuk.