Készítette: Major Péter Fájlkezelés .Net-ben Készítette: Major Péter
Előszó Az alábbi diavetítés tömören összefoglalja a .Net-es fájlkezelés sajátosságait. A diák számos parancsot ismertetnek, de ezeket nem kell fejből tudni, hiszen azokat a Visual Studio bevitelkor felajánlja, megjeleníti azok feladatát, és paramétereik jelentését (angolul). Ennél sokkal fontosabb, hogy tudjuk milyen parancsok vannak, hiszen ezen építőelemekből építjük fel programunkat. Ha egy parancson állva F1-et nyomunk megjelenik annak részletes leírása, sok esetben példaprogrammal együtt. Itt nem kerül ismertetésre az összes lehetőség, ezért a kódkiegészítő listáit és a súgót böngészve mindig érdemes szétnézni, mielőtt egy esetleg már megoldott probléma implementálásába kezdünk. (Persze ettől még mi is megoldhatjuk a problémát, hiszen sok esetben több funkciót vagy nagy sebességet csak így kaphatunk.)
Bevezetés
A System.IO névtér A .Net keretrendszerben a fájlrendszerrel kapcsolatos műveleteket magába foglaló névtér a System.IO. Osztályai többek közt az alábbi funkciókat valósítják meg: Fájlok: Létrehozása, törlése Mozgatása/Átnevezése Írása, olvasása Fájlinformációk (pl.: módosítás ideje) elérése Könyvtárak: Létrehozása, törlése, mozgatása Könyvtárinformációk (pl.: létrehozás ideje) elérése Elérési utak kezelése (Path osztály) Adatfolyamok kezelésének alaposztályait definiálja, amit pl.: a System.Net névtér számos eleme használ
Elérési utak kezelése Az elérési utakban található „\” (backslash) karakter a C# nyelvben speciális szerepet játszik, úgynevezett menekülő karakter, mely az utána lévő karakter speciális kezelését vonja maga után. Ez teszi lehetővé pl.: " elhelyezését egy stringben vagy új sor kezdését: "Hello \"world\"!" "Új sor:\r\n" Ezért az elérési utakat így adhatjuk meg: string path = "F:\\Axodox\\Dokumentumok\\E-book"; string path2 = @"F:\Axodox\Dokumentumok\E-book";
A Path osztály A fájlkezelő műveletek során sokszor szükség van elérési utak manipulációjára, ezt a System.IO.Path statikus osztály hívatott megkönnyíteni. Az alábbi függvényekkel egy elérési utat tároló stringből nyerhetőek ki információk: string GetDirectoryName(string path): elérési út a fájlnév és az utolsó perjel nélkül string GetFileName(string path): a fájl neve kiterjesztéssel string GetFileNameWithoutExtension(string path): a fájl neve kiterjesztés nélkül string GetExtension(string path): a kiterjesztés
Könyvtárkezelés
A Directory és File statikus osztályok A System.IO.Directory és System.IO.File osztályok a könytár- és fájlkezeléssel kapcsolatos műveleteket valósítják meg. (Olyanokra kell gondolni pl.: Windows Intézőben fájlmásolás, könyvtárlétrehozás stb.) Statikus osztályok, nem példányosíthatóak, tehát függvényeik pl.: a Directory.GetCurrentDirectory() formában hívhatóak. (using System.IO; esetén) A következőkben röviden összefoglalom az ide kötődő függvényeket, melyek feladatát nevük is jól tükrözi. Itt most csak a fontosabb függvényeket ismertetem, a többit név alapján is könnyen kiválaszthatjuk VisualStudio-ban a Ctrl+Space lenyomásával előugró listából.
A Directory osztály fontosabb tagfüggvényei DirectoryInfo CreateDirectory(string path): a megadott elérési út összes nem létező elemét létrehozza void Delete(string path[, bool recursive]): törli a megadott könyvtárat (ha üres!), ha a „recursive” igaz, akkor az összes bennfoglalt fájlt és könyvtárat is törli bool Exists(string path): lekérdezi, hogy létezik-e a könyvtár string[] GetDirectories(string path): megadja a könyvtárban található alkönyvtárak listáját (teljes elérési úttal) string[] GetFiles(string path[, string searchPattern]): megadja a könyvtárban található fájlok listáját (teljes elérési úttal), a „searchPattern” paraméterrel „munka?_*.txt” stílusban szűrhetünk is string[] GetFileSystemEntries(string path): az előző kettő együtt
A Directory osztály fontosabb tagfüggvényei string GetCurrentDirectory(): az aktuális könyvtárat adja vissza (Ez nem mindig egyezik meg a jelenleg futó program (*.exe, *.dll) helyével, amit egyébként a System.Reflection.Assembly. GetExecutingAssembly().Location paraméterből nyerhetünk ki) void Move(string sourceDirName, string destDirName): a könyvtár mozgatása a forrásból (sourceDirName) a célkönyvtárba (destDirName)
A File osztály fontosabb tagfüggvényei bool Exists(string path): ellenőrzi a megadott fájl létezését void Move(string sourceFileName, string destFileName): a fájl mozgatása forrásból a célba void Copy(string sourceFileName, string destFileName): a fájl másolása forrásból a célba (létező fájl így nem írható felül) void Delete(string path): fájl törlése A többi fontosabb tagfüggvény később kerül ismertetésre.
Szöveges Fájlok írása / olvasása
Karakterkódolás A szöveges fájlok különféle módon tárolhatják bináris formában a szöveget. A kódolások egyik fő jellemzője, hogy egy karaktert hány bájton tárolnak, hiszen ez szabja meg, hogy hány különféle betű és jel használható. A legelterjedtebb karakterkódolások: ASCII - American Standard Code for Information Interchange – 1bájt / kar., első 128 állandó jel, a többi a nyelvi beállítástól függ (ő, ű betűk) UTF - Unicode Transformation Format – 256^4 jel, több változata létezik – de a legfontosabb az UTF-8 (és még az UTF-7): Kompatibilis az ASCII-vel mivel az első 128 jel kódolása megegyezik 1-4 bájt / karakter (gyakori karakterekhez rövidebb jel) Ma az egyik legelterjedtebb kódolás, mert helytakarékos és a teljes UTF karakterkészletet képes leírni.
Karakterkódolás a .NET-ben A System.Text.Encoding osztály tagjaival, pl.: Encoding.ASCII Encoding.UTF8 Minden osztálynak az Encoding-ban van egy: byte[] GetBytes(string text): visszaadja a text-nek megfelelő bájtömböt az adott kódolásban string GetString(byte[] bytes, int index, int count): visszaadja a bytes tömbnek megfelelő string-et (az „index” a kezdő index a bájttömbben, a „count” a kikódolandó bájtok száma) Ezen osztály elemeit kell megadni számos a szövegfájlokat kezelő parancsnak.
Szöveges fájlok kezelése Bár az adatok tárolása bináris formában hatékonyabb lehet, mint szöveges leírással, ennek ellenére számos esetben fontos szempont az, hogy egy fájlt pl.: notepad-el megnyitva is könnyen olvashassunk szerkeszthessünk. A szöveges fájlok kezelésére több lehetőségünk van: Egy egész fájl létrehozása/olvasása/írása egyetlen paranccsal egy string-ből/be (ReadAllText stb.) Kezelés adatfolyamként (StreamReader, StreamWriter) A .Net bináris fájlok kezelésére szolgáló parancsaival is írhatunk szöveget fájlba. (úgy hogy pl. notepad-ban az látható lesz)
Egyszerű szövegfájl kezelési parancsok void WriteAllLines( string path, string[] contents[, Encoding encoding] ): szöveg fájlba írása egy string tömbből (contents), a megadott kódolással (encoding), felülír ha már létezik string[] ReadAllLines( string path, Encoding encoding ): szövegfájl beolvasása tömbbe, melynek minden eleme egy sor void WriteAllText( string path, string contents, Encoding encoding ): egyetlen string fájlba írása, felülír ha már létezik string ReadAllText( string path, Encoding encoding ): szövegfájl beolvasása stringbe void AppendAllText( string path, string contents ): szöveg hozzáadása a fájl végéhez, ha nem létezik létrehozza Ezen parancsokat egyszerűbb esetekben használhatjuk.
Az adatfolyamok elméleti háttere Az adatfolyam tulajdonképpen egy adatsorozatot (bájtsorozatot, karaktersorozatot) jelképező absztrakció. Egy adatfolyam a következő funkciókat nyújtja: Adatokat olvashatunk ki valamilyen típusú változóba. Egy változó tartalmát kiírhatjuk az adatfolyamba. Az írás/olvasás pozíció változással jár, az hogy ezen kívül mozoghatunk-e az attól függ mit képez le az adatfolyam: Például egy szövegfájlban tetszőleges mozoghatunk (a fájl határán belül). De egy TCP adatfolyam olvasásakor ezt nyilván nem tehetjük meg.
Fájl kezelés folyamata A fájlt először meg kell nyitnunk, ilyenkor meg kell adni annak helyét, a megnyitás módját (írás/olvasás), és megadható a fájl zárolása. (pl.: miközben írunk egy nagy fájlt azt közbe ne lehessen megnyitni) A fájl írása/olvasása, mozgás a fájlban. Flush művelet: íráskor az adatok nem kerülnek közvetlenül a háttértárra, hanem egy ideig a memóriában pufferelődnek, ha kell kényszeríthetjük az adatok kiírását (nagy fájlok, hibanapló). A fájl bezárása (automatikusan flush) és az erőforrások felszabadítása. Fájl megynyitás / létrehozás Adatok olvasása / írása Fájl bezásrása
A StreamReader osztály A System.IO.StreamReader osztály segítségével szövegfájlok tartalmát olvashatjuk be. Főbb tagfüggvényei és mezői: StreamReader( string path, Encoding encoding ): a konstruktornak a fájl elérési útját és a használandó karakterkódolást kell megadni int Read( char[] buffer, int index, int count ): beolvasás karaktertömbbe, az „index” a céltömbre vonatkozik, a „count” a kiolvasandó karakterek maximális száma, a visszatérési érték a kiolvasott elemek száma (Az előző parancsnak van egy ReadBlock változata is, amely ugyanilyen formájú, a különbség abban rejlik, hogy ez addig blokkolja az adott szálat, amíg nem áll rendelkezésre elég adat, ennek pl.: hálózati adatfolyamok esetében van jelentősége.)
A StreamReader osztály string ReadLine(): egy sor beolvasása string ReadToEnd(): az egész adatfolyam beolvasása bool EndOfStream { get; }: megadja, hogy végére értünk-e az adatfolyamnak void Close(): az adatfolyam lezárása, erőforrások felszabadítása (meghívja a Dispose-t is)
A StreamWriter osztály A System.IO.StreamWriter osztállyal egyszerűen elvégezhető szövegfájlok létrehozása. Főbb tagfüggvényei és mezői: StreamWriter( string path, bool append, Encoding encoding ): konstruktor, a „path”-ban megadott fájlt megnyitja írásra, az „append” bekapcsolásával létező fájlhoz fűzhetünk hozzá, nélküle ilyenkor felülírás történik, az „encoding”-al megadhatjuk a szöveg kódolását void Write( string value ): adat kiírása adatfolyamba, számos túltöltött verzióval rendelkezik sok adattípushoz (int, double, bool stb.) void WriteLine( string value ): ugyanaz, mint az előző, de sortörést is beszúr az adatfolyamba
A StreamWriter osztály void Flush(): kényszeríti az adatok azonnali kiírását a adatfolyamba bool AutoFlush { get; set; }: automatikus adatkiírás az adatfolyamba void Close(): az adatfolyam lezárása, erőforrások felszabadítása (meghívja a Dispose-t is) Az itt ismertetett osztályokkal általános (System.IO.Stream-ből leszármazó) adatfolyamba is írhatunk, ekkor a konstruktor egy másik formáját használjuk.
Egyszerű példaprogram using System; using System.IO; using System.Text; namespace gyak_file { class Program static void Main(string[] args) //A második paraméter: a hozzáfűzés StreamWriter SW = new StreamWriter("text.txt", false, Encoding.UTF8); SW.Write("Ez egy szövegfájl."); SW.WriteLine(" Új sort kéne kezdeni."); SW.Write("Amit így is lehet:\r\n"); SW.WriteLine("Itt a vége?"); //A parancsok az összes fontos .Net típushoz tartalmaznak overloadot SW.WriteLine(true); SW.Flush(); //Ha itt hiba miatt leállna a program futása, //a flush miatt az eddig beadott szöveg már kiolvasható lenne a fájlból. SW.WriteLine("Viszlát!"); SW.Close(); SW.Dispose(); } //A létrejött fájl tartalma: Ez egy szövegfájl. Új sort kéne kezdeni. Amit így is lehet: Itt a vége? True Viszlát!
Feladat Ön egy kisvállalkozásnál dolgozik, mint tervezőmérnök. Az esetleges adatvesztések elkerülése miatt főnöke megkéri, hogy az összes munkáját rendszeresen mentse át egy központi szerverre. Ön több egymástól különböző könyvtárban található fájlokon dolgozik egyszerre. (pl.: egy végeselemes modellt készít egy számítógéppel tervezett alkatrészről, és közben jelentést is ír róla.) A gyakori másolgatás folyamatosan megszakítja munkájában. Eszébe jut, hogy egyszerűbb lenne egy célprogramot használni a biztonsági mentésekhez, és az egyetemen szerzett C# ismeretei felhasználásával ezt el is tudná készíteni. BackUpTools (+)
BackUpTools Készítsen programot, mely egy szöveges fájlból beolvassa, milyen könyvtárakból, hová, milyen típusú fájlokat kell átmásolnia. A másolást csak akkor végezze el, ha az szükséges: Ha még nincs biztonsági másolat a fájlról Vagy van másolat, de az régebbi, mint az eredeti A program ne tartalmazzon grafikus felületet, hiszen a háttérben fog futni, így feleslegesen foglalna le erőforrásokat. A program a folyamat állapotát terminálablakban mutassa és rögzítse naplófájlba is. A feladatot tartalmazó fájlt argumentumként lehessen megadni a program elindításakor, így azt a Windows feladatütemezőhöz adva az automatikus biztonsági mentés megoldható. BackUpTools (+)
Egyszerű naplózó osztály készítése public class Log { public bool Closed { get; private set; } //Lezártuk-e már a fájlt? StreamWriter SW; //A szöveg fájl írásához szükséges StreamWriter bool Error; //Hiba történt //Gyárfüggvény //Azért használunk gyárfüggvényt, mert ha eleve nem tudjuk a fájlt létrehozni, //nincs mit csinálni az osztállyal public static Log FromFile(string path, Encoding encoding) try { //Megnyitás hozzáfűzésre(!) StreamWriter streamWriter = new StreamWriter(path, true, encoding); return new Log(streamWriter); } catch //Ha hiba történt null-t adunk vissza return null; private Log(StreamWriter streamWriter) //Privát(!) konstruktor a gyárfüggvényhez Error = false; SW = streamWriter; Add("Log opened."); … BackUpTools (+) //Az osztály használata: log = Log.FromFile("log.txt", Encoding.UTF8); log.Add("Napló szöveg 1"); log.Close();
Egyszerű naplózó osztály készítése … public void Add(string text) //Szöveg hozzáadása { if (!Closed) //Ha még nem zártuk le try string line = DateTime.Now.ToString() + ": " + text; if (!Error) //Ha hiba volt ne próbáljunk írni SW.WriteLine(line); //Kiírás fájlba SW.Flush(); //Flush ha hiba lenne } Console.WriteLine(line); //Kiírás konzolba catch (Exception e) //Ha gond van zárjuk le a fájlt Error = true; //Hiba történt Add("Log error: " + e.Message); //Ez csak a konzolba ír (Error == true) Close(); public event EventHandler Closing; //Esemény lezáráskor public void Close() //Fájl lezárása Add("Log closed."); Closed = true; SW.Close(); //Erőforrások felszabadítása, meghívja a Dispose-t is if (Closing != null) Closing(this, null); //Eseménykezelő hívása Egyszerű naplózó osztály készítése BackUpTools (+)
Könyvtárfa másoló algoritmus //Könyvtárfa másolás srcPath-ból dstPath-ba a fileFilter fájlszűrő alkalmazásával public static void TreeCopy(string srcPath, string dstPath, string fileFilter) { Queue<string> dirsToCopy = new Queue<string>(); //A másolandó könyvtárak sora dirsToCopy.Enqueue(srcPath); //A kiinduló könyvtár elhelyezése a listában //Perjel adása az elérési út végéhez, ha nincs if (dstPath[dstPath.Length - 1] != '\\') dstPath += '\\'; string path, relPath; //A teljes és a relatív elérési út string[] files, dirs; //Az adott mappában található fájlok és könyvtárak //Az eredeti elérési út hossza; mivel minden könyvtár és fájl ez alatt van, //ezért azok elérési útjának ezen a hosszon túlnyúló része adja a relatív elérési utat. int sourcePathLength = srcPath.Length; while (dirsToCopy.Count > 0) //Amíg van mit másolni path = dirsToCopy.Dequeue(); //Elem kiolvasása a sorból relPath = path.Remove(0, sourcePathLength); //Relatív elérési út számítása if (!Directory.Exists(dstPath + relPath)) //Ha a cél útvonal még nem létezik try //akkor megpróbáljuk létrehozni Directory.CreateDirectory(dstPath + relPath); } catch log.Add("Creation of directory \"" + dstPath + relPath + "\" was unsuccessful."); … BackUpTools (+) //Az osztály tesztelésekor ne feledkezzünk meg, egy Log típusú //log változó deklarálásáról az osztályban. //Ennek inicializációja: log = Log.FromFile("log.txt", Encoding.UTF8); //És lezárása: log.Close();
Könyvtárfa másoló algoritmus … try { //Az adott könyvtárban található fájlok lekérdezése files = Directory.GetFiles(path, fileFilter); log.Add("Directory opened: " + (relPath == "" ? path : relPath)); for (int i = 0; i < files.Length; i++) try //Lemásoljuk a fájlokat //DifferentialCopy(files[i], dstPath + files[i].Remove(0, sourcePathLength)); File.Copy(files[i], dstPath + files[i].Remove(0, sourcePathLength)); } catch log.Add("Cannot copy file at " + files[i]); //Az adott könyvtárban található könyvtárak lekérdezése dirs = Directory.GetDirectories(path); for (int i = 0; i < dirs.Length; i++) //Hozzáadás a másolandó listához dirsToCopy.Enqueue(dirs[i]); log.Add("Cannot read directory at " + path); BackUpTools (+)
Differenciális fájlmásolás //Differenciális fájmásolás (srcFile: forrásfájl, dstFile: célfájl) //Csak akkor másol, ha a célfájl nem létezik, //vagy létezik, de régebbi, mint a forrásfájl public static void DifferentialCopy(string srcFile, string dstFile) { if (!File.Exists(dstFile) || File.GetLastWriteTimeUtc(srcFile) > File.GetLastWriteTimeUtc(dstFile)) File.Copy(srcFile, dstFile); //ProgressCopy(srcFile, dstFile); log.Add("File backed up: " + Path.GetFileName(srcFile)); } else log.Add("File skipped: " + Path.GetFileName(srcFile)); BackUpTools (+)
Főprogram BackUpTools (+) //A BackUpTools főprogramja, //parancsori paraméterként megadandó a feladatfájl static void Main(string[] args) { //Naplófájl létrehozása log = Log.FromFile("log.txt", Encoding.UTF8); if (args.Length == 0) //Ha nincs megadva feladatfájl log.Add("You must specify an input file!"); } else string path = args[0]; //Ha az elérési út relatív, abszolút út meghatározása if (!Path.IsPathRooted(path)) path = Directory.GetCurrentDirectory() + '\\' + path; if (File.Exists(path)) log.Add("Back up started..."); //Feladat fájl beolvasása StreamReader SR = new StreamReader(args[0], Encoding.UTF8); string[] line; //Amíg nem vagyunk a fájl végén while (!SR.EndOfStream) //Sorok feldarabolása paraméterekké a "|" (bar) karakterrel //Feladat fájl felépítése: forrásKönyvtár|célKönyvtár|FájlSzűrő line = SR.ReadLine().Split('|'); if (line.Length == 3) //Könyvtárfa másolása TreeCopy(line[0], line[1], line[2]); log.Add("Back up complete."); log.Add("The file \"" + path + "\" cannot be found."); log.Close(); //Napló lezárása Console.ReadLine(); Főprogram BackUpTools (+)
Futási argumentumok megadása BackUpTools (+)
Feladat fájl A feladat fájl mintája: Elhelyezése az előző dia argumentuma szerint: \BackUpTools\BackUpTools\bin\Debug Tipp: Projektkönyvtár megnyitása legegyszerűbb, ha a Solution Explorer-ben a projektnévre jobb gombbal kattintva a helyi menüből kiválasztjuk az Open Folder in Windows Explorer menüpontot. F:\Axodox\Dokumentumok\Visual Studio 2008\Projects\Minesweeper|E:\BackUp\Minesweeper|*.* F:\Axodox\Dokumentumok\Visual Studio 2008\Projects\DeepView|E:\BackUp\DeepView|*.* H:\Sintel.2010.2K.SURROUND.x264-VODO|E:\BackUp|*.mp4 BackUpTools (+)
Fájlkezelő dialógusok
Fájlkezelő dialógusok A .Net lehetőséget ad a Windows részét képező fájl megnyitó / mentő és mappaválasztó dialógusainak használata. Ezen elemeket ablaktervező módban is létrehozhatjuk, jellegükből fakadóan azonban nem az ablakon foglalnak helyet. A tervező nézetben az ablak alatt egy elkülönített sávban jelennek meg. Ezen dialógusok használata igen előnyös, hiszen egyrészt a felhasználók a már jól ismert kezelőfelületen dolgozhatnak, másrészt pedig számos funkciót kellene implementálni hiányukban.
A fájlmegnyitás dialógus System.Windows.Forms.OpenFileDialog Fő mezői és metódusai: DialogResult ShowDialog (): a dialógus megjelenítése, ha a visszatérési érték DialogResult.OK, akkor a felhasználó az OK gombra kattintva zárta be az ablakot. string FileName { get; set; }: a kiválasztott fájl elérési útja string Title { get; set; }: az ablak fejlécének szövege string Filter { get; set; }: fájl típusok felsorolása a „Szöveg fájlok (*.txt)|*.txt|Képek|*.png;*.bmp;*.jpg” formában int FilterIndex { get; set; }: a kiválasztott szűrő sorszáma 0-tól bool CheckFileExists { get; set; }: csak létező fájlt lehessen megadni bool CheckPathExists { get; set; }: csak létező elérési út adható meg string DefaultExt { get; set; }: szokásos kiterjesztés, ha a felhasználó nem adja meg, akkor ez hozzáfűződik a fájlnévhez string InitialDirectory { get; set; }: kiinduló elérési út bool Multiselect { get; set; }: több fájl kijelölhetősége string[] FileNames { get; }: a fájlok eléri útja (több fájl kijelölése esetén) void Dispose(): az erőforrások felszabadítása
A fájlmentés dialógus System.Windows.Forms.SaveFileDialog Fő mezői és metódusai: DialogResult ShowDialog (): a dialógus megjelenítése, ha a visszatérési érték DialogResult.OK, akkor a felhasználó az OK gombra kattintva zárta be az ablakot. string FileName { get; set; }: a kiválasztott fájl elérési útja string Title { get; set; }: az ablak fejlécének szövege string Filter { get; set; }: fájl típusok felsorolása a „Szöveg fájlok (*.txt)|*.txt|Képek|*.png;*.bmp;*.jpg” formában int FilterIndex { get; set; }: a kiválasztott szűrő sorszáma 0-tól bool CheckFileExists { get; set; }: csak létező fájlt lehessen megadni bool CheckPathExists { get; set; }: csak létező elérési út adható meg bool OverwritePrompt { get; set; }: rákérdezés a felülírásra string DefaultExt { get; set; }: szokásos kiterjesztés, ha a felhasználó nem adja meg, akkor ez hozzáfűződik a fájlnévhez string InitialDirectory { get; set; }: kiinduló elérési út void Dispose(): az erőforrások felszabadítása
A könyvtárválasztó dialógus System.Windows.Forms.FolderBrowserDialog Fő mezői és metódusai: DialogResult ShowDialog (): a dialógus megjelenítése, ha a visszatérési érték DialogResult.OK, akkor a felhasználó az OK gombra kattintva zárta be az ablakot. string SelectedPath { get; set; }: a kiválasztott könyvtár eléri útja bool ShowNewFolderButton { get; set; }: az új könyvtár gomb megjelenítése string Description { get; set; }: szöveg a könyvtárválasztó ablakrész felett Environment.SpecialFolder RootFolder { get; set; }: kiinduló rendszerkönyvtár (pl.: Dokumentumok, Képek, lásd az System.Environment.SpecialFolder enumerációt) void Dispose(): az erőforrások felszabadítása
Feladat Középiskolás ismerőse MSN-en meséli, hogy irodalomórán olyan feladatot kaptak, hogy keressenek ki fontos motívumokat Petőfi Sándor költészetéből, ezért nem ér rá tovább chatelni, mert könyvtárba kell mennie. Önnek eszébe jut, hogy Petőfi Sándor összes költeménye megtalálható a magyar elektronikus könyvtárban is, így el is küldi neki a megfelelő txt fájlt. Közben eszébe jut, hogy könnyen írhatna olyan programot, mely megkeresi a leggyakoribb kifejezéseket a szövegben. Röviden: írjon programot, mely egy txt fájlt beolvasva megkeresi az abban leggyakrabban előforduló kifejezéseket. Petofi (+)
Kezelőfelület MainFrom LBData BOpen Helyezzen el egy fájlmegnyitó ablakot (OpenFileDialog) is! Name: OFD Filter: Szöveges fájlok (*.txt)|*.txt Title: Válasszon egy szövegfájlt Petofi (+)
Petőfi 1. Petofi (+) using System; using System.Collections.Generic; using System.ComponentModel; using System.Text; using System.Windows.Forms; using System.IO; namespace Petofi { public partial class MainForm : Form //A szövegösszehasonlító osztály //a szavak gyakorisága szerinti csökkenő sorrendbe rendezéshez class WordComparer : IComparer<KeyValuePair<string, int>> public int Compare(KeyValuePair<string, int> x, KeyValuePair<string, int> y) if (x.Value == y.Value) return 0; if (x.Value > y.Value) return -1; else return 1; } WordComparer WC; //Konstruktor public MainForm() InitializeComponent(); WC = new WordComparer(); Petofi (+)
Petőfi 2. Petofi (+) //A fájlválasztás gomb eseménykezelője private void BBrowse_Click(object sender, EventArgs e) { //Ha a felhasználó OK-kal zárta a dialógust if (OFD.ShowDialog() == DialogResult.OK) LBData.Items.Clear(); //Az eredménylista ürítése //Szótár a szavaknak Dictionary<string, int> Words = new Dictionary<string, int>(); //Szöveg olvasása fájlból StreamReader SR = new StreamReader(OFD.FileName,Encoding.UTF7); string line; //egy sor szöveg string[] lineWords; //egy sor szavai while (!SR.EndOfStream) //amíg nem érünk végig a fájlon //Kisbetűssé alakítás line = SR.ReadLine().ToLower(); //Feldarabolás szavakká lineWords = line.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < lineWords.Length; i++) //Írásjelek eltávolítása lineWords[i] = lineWords[i].Trim(new char[] { ',', '.', ';', '!', '?' }); if (Words.ContainsKey(lineWords[i])) //Ha benne van a szótárban növeljük, //hogy hányszor szerpelt eddig Words[lineWords[i]]++; } else //Ha nincs a szótárban hozzáadjuk Words.Add(lineWords[i], 1); … Petőfi 2. Petofi (+)
Petőfi 3. Petofi (+) … //A lista hossza max 400 szó int listLength = (Words.Count < 400 ? Words.Count : 400); //Mivel szótárat nem lehet sorbarakni, //mivel abban az elemek sorrendje nem értelmezett, //ezért csinálunk egy listát a rendezéshez List<KeyValuePair<string, int>> wordList = new List<KeyValuePair<string, int>>(Words.Count); wordList.AddRange(Words); //Szavak hozzáadása wordList.Sort(WC); //Rendezés //Mivel sok elemet adunk a listához, kikapcsoljuk az újrarajzolást LBData.SuspendLayout(); for (int i = 0; i < listLength; i++) { //Eredmények megjelenítése LBData.Items.Add(string.Format("{0}. {1} ({2})", new object[] { (i + 1), wordList[i].Key, wordList[i].Value })); } //Visszakapcsoljuk a lista újrarajzolását LBData.ResumeLayout(); Petofi (+)
Bináris fájlok írása / olvasása
Bináris fájlkezelés Bináris fájlkezelésről, akkor beszélünk, ha a fájlok tartalmát egy byte-tömbként kezeljük. Ez nyilvánvalóan csak akkor célszerű, ha adatainkat byte tömb formában is fel tudjuk írni. Bár a számítógép nyilvánvalóan így tárolja a memóriában a változók értékét, ezeket .Net- ben közvetlenül csak a BitConverter osztállyal, konvertáló parancsokkal, esetleg unsafe context-ben mutatókkal érhetjük el. Erre azonban nincs szükség a BinaryReader ill. a BinaryWriter osztály használatakor.
Ismétlés: A BitConverter osztály A System.BitConverter statikus osztály biztosítja adatok byte tömbbé alakítását (oda-vissza). ToBoolean / ToChar / ToDouble / ToInt16 (short) / ToInt32 (int) / ToInt64 (long) / ToSingle (float) / ToString / ToUInt16 (usort) / ToUInt32 (uint) / ToUInt64 (ulong): konvertálás adott adattípusba pl.: int ToInt32 ( byte[] value, int startIndex ): a „value” tömbből a „startindex”-től kiolvas az adattípusnak megfelelő 4 bájtot és integerként visszaadja
Ismétlés: Átalakítás bináris formátumba byte[] GetBytes ( bool / char / short / int / long / single / double / ushort / uint / ulong value ): visszaadja a „value” változó értékét byte-tömb formájában A visszaadott tömb hossza nyilván az adattípustól függ. (1 byte: bool; 2 byte: char (UTF-16!), short, ushort; 4 byte: int, uint, single; 8 byte: double, long, ulong) Stringet a System.Text.Encoding osztály elemeivel konvertálunk. Ez a módszer akkor célszerű adatfolyamba íráskor / olvasáskor, ha csak néhány elemet írunk / olvasunk, különben a BinaryReader / BinaryWriter osztályt érdemes használni.
Egyszerű fájlba író/fájlból olvasó parancsok a File osztályból void WriteAllBytes(string path, byte[] bytes): egy byte tömb kiírása fájlba, felülír ha már létezik byte[] ReadAllBytes(string path): teljes fájl beolvasása byte tömbbe
A FileStream osztály A fájlok bináris elérését a System.IO.FileStream osztály biztosítja. Segítségével fájlok írása és olvasása is egyszerűen megvalósítható. Bár itt nem foglalkozunk vele, érdemes kiemelni a System.IO.MemoryStream osztályt, amely kontruktorát leszámítva megegyezik ezzel az osztállyal, de az adatokat a memóriában tárolja, ez sok esetben lehet hasznos (pl.: pufferelés gyorsabb eléréshez). Ezért célszerű lehet egyes függvényeinket a System.IO.Stream osztályra megírni, mivel az előbbi osztály is ebből származik, így univerzálisabban használható függvényeket kapunk.
Fájlok megnyitása FileStream ( string path, FileMode mode, FileAccess access): konstruktor, ahol: path: a fájl elérési útja mode: a fájl megnyitásának módja (lásd később) access: a fájl használatának módja (lásd később)
Fájlok megnyitása A fájlok megnyitásakor meg kell adni, hogyan akarjuk megnyitni őket. A módokat a System.IO.FileMode enumeráció tartalmazza: CreateNew: új fájl létrehozása, ha már létezik kivétel keletkezik Create: új fájl létrehozása, ha létezik felülírja Open: létező fájl megnyitása OpenOrCreate: létező fájl megnyitása, ha nem létezik létrehozása Truncate: létező fájl megnyitása, de tartalmát töröljük Append: létező fájlt megnyitja és a végére ugrik, ha nem létezik létrehozza (figyelem a fájl csak írásra használható ilyenkor, a fájl eredeti vége előtt pedig írni sem lehet)
Fájlok megnyitása A fájlok megnyitásakor azt is meg kell adni, hogyan akarjuk használni őket. Ezt a System.IO.FileAccess enumerációval tehetjük meg: Read: csak olvasni lehet a fájlból Write: csak írni lehet a fájlba ReadWrite: írni és olvasni is lehet
A FileStream osztály tagjai A FileStream osztály fontosabb tagfüggvényei és mezői: int Read ( byte[] array, int offset, int count ): olvasás byte tömbbe az „offset” indextől (a tömbben), maximum „count” karakterig, a visszatérési érték a kiolvasott karakterek száma void Write ( byte[] array, int offset, int count ): byte tömb írása fájlba, az „offset” indextől (a tömbben), a „count”-al a kiírandó karakterek száma adható meg
A FileStream osztály tagjai long Seek ( long offset, SeekOrigin origin ): mozgás a fájlban az „offset” pozícióra (bájtban mérve), a System.IO.SeekOrigin típusú „origin” megadja, hogy mihez viszonyítjuk a pozíciót, ezen enumeráció tagjai a következők: Begin: a fájl eleje Current: a jelenlegi pozíció End: a fájl vége A visszatérési érték az új pozíció.
A FileStream osztály tagjai long Length { get; }: a fájl hossza bájtokban long Position { get; set; }: az aktuális pozíció bájtokban void Flush (): pufferben lévő adatok kiírása void Close(): az adatfolyam lezárása, erőforrások felszabadítása (meghívja a Dispose-t is)
Feladat Egészítse ki a BackUpTools programot olyan a fájlmásoló metódussal, mely a terminálablakban kijelzi a folyamat haladását (a fájl hány %-a lett lemásolva). Használja a FileStream osztály metódusait. A másolás során használjon puffert, melynek mérete 1MB legyen. BackUpTools (+)
Fájlmásolás folyamatkijelzéssel const int bufferLength = 1024*1024; //1MB puffer public static void ProgressCopy(string srcFile, string dstFile) { //A kurzor eredeti pozíciójának letárolása int cusorTop = Console.CursorTop; int cusorLeft = Console.CursorLeft; //Bemenet és kimenet megnyitása FileStream srcFS = new FileStream(srcFile, FileMode.Open, FileAccess.Read); FileStream dstFS = new FileStream(dstFile, FileMode.Create, FileAccess.Write); try //Puffer a fájlmásoláshoz byte[] buffer = new byte[bufferLength]; int readedBytes = 0; //kiolvasott béjtok száma while (srcFS.Position < srcFS.Length) //Amíg nem érünk végig a forrásfájlon //Olvassunk a pufferbe a forrásfájlból readedBytes = srcFS.Read(buffer, 0, bufferLength); //Majd beírjuk a kiolvasott bájtokat a célfájlba dstFS.Write(buffer, 0, readedBytes); dstFS.Flush(); //Változások mentése a háttértárra Console.SetCursorPosition(cusorLeft, cusorTop); //Kurzor pozició visszaállítása //Folyamatállapot kijelzése Console.Write("Copying... " + (int)(srcFS.Position * 100 / srcFS.Length) + "% complete."); } finally //Fájlok bezárása srcFS.Close(); dstFS.Close(); //A kurzor visszaállítása és a kiírt szöveg eltüntetése Console.SetCursorPosition(cusorLeft, cusorTop); Console.WriteLine("".PadRight(Console.WindowWidth - cusorLeft)); Fájlmásolás folyamatkijelzéssel BackUpTools (+)
A BinaryReader osztály A System.IO.BinaryReader osztály segítségével könnyel olvashatunk ki különböző típusú adatokat bináris fájlokból. Segítségével fájlból közvetlenül pl.: double típusú változóba olvashatunk. Használatához szükség van egy adatfolyamra, konstruktora: BinaryReader( Stream input[, Encoding encoding] ): ahol megadhatjuk a bemeneti adatfolyam mellett, a szöveg kiolvasásnál használt kódolást is.
A BinaryReader osztály Az adatok kiolvasására a következő parancsok szolgálnak: ReadBoolean / ReadByte / ReadBytes / ReadChar / ReadChars / ReadDecimal / ReadDouble / ReadInt16 / ReadInt32 / ReadInt64 / ReadSByte / ReadSingle / ReadString / ReadUInt16 / ReadUInt32 / ReadUInt64 A parancsok a kiolvasott értéket adják vissza a nevükből is látható típusban. A hagyományos bájttömbbe olvasás a következő paranccsal érhető el: int Read( byte[] buffer, int index, int count ) A használat után hívjuk meg a void Close() metódust, ami meghívja a használt adatfolyam Close() metódusát is.
A BinaryWriter osztály A System.IO.BinaryWriter osztály egyszerű .Net adattípusok fájlba írására szolgál. Konstruktor: BinaryWriter( Stream output, Encoding encoding ): a paraméterek a kimeneti adatfolyam és a karakterkódolás Fő metódusa: void Write( bool / byte / byte[] / char / char[] / decimal / double / short / int / long / sbyte / float / string / ushort / uint / ulong value): adott típusú változó értékének adatfolyamba írása
A BinaryWriter osztály Ugyanúgy pozícionálunk, mint minden adatfolyamban: long Seek( int offset, SeekOrigin origin ) A puffer fájlba írását a void Flush() metódussal kényszeríthetjük. A használat után hívjuk meg a void Close() metódust, ami meghívja a használt adatfolyam Close() metódusát is.
Feladat Készítsen archiváló programot, mely képes egy könyvtárfát egyetlen fájlba elmenteni ill. egy fájlból egy könyvtárfát ki tud bontani. Kiindulópontként használhatja a BackUpTools TreeCopy metódusát valamint a BinaryReader és BinaryWriter osztályokat. BackUpTools (+)
Fájl írása adatfolyamba //A puffer mérete: MB const int BUFFERLEN = 1024 * 1024; //Fájl írása adatfolyamba public static void WriteFileToStream(Stream FS, string path) { //Bemeneti fájl megnyitása FileStream iFS = new FileStream(path, FileMode.Open, FileAccess.Read); //Fájl hosszának kiírása FS.Write(BitConverter.GetBytes(iFS.Length), 0, 8); //Fájl bemásolása byte[] data = new byte[BUFFERLEN]; int readedBytes; while (iFS.Position < iFS.Length) readedBytes = iFS.Read(data, 0, BUFFERLEN); FS.Write(data, 0, readedBytes); } iFS.Close(); BackUpTools (+)
Fájl kiírása adatfolyamból //Fájl írása adatfolyamból public static void ReadStreamToFile(Stream FS, string path) { //Fájlhossz kiolvasása byte[] data = new byte[8]; FS.Read(data, 0, 8); long len = BitConverter.ToInt64(data, 0); //Célfájl megnyitása FileStream oFS = new FileStream(path, FileMode.Create, FileAccess.Write); //Fájl kimásolása data = new byte[BUFFERLEN]; int readedBytes; long endPos = FS.Position + len; while (FS.Position < endPos) if (endPos - FS.Position < BUFFERLEN) readedBytes = FS.Read(data, 0, (int)(endPos - FS.Position)); } else readedBytes = FS.Read(data, 0, BUFFERLEN); oFS.Write(data, 0, readedBytes); oFS.Close(); BackUpTools (+)
Könyvtárfa archiválása //Az adatblokk tipusa enum ArchiveBlockType : byte { Directory = 0, File = 1, Data = 2 }; //Könyvtárfa archiválása fájlba public static void Archive(string srcPath, string dstFile, string fileFilter) { try //Célfájl megnyitása FileStream FS = new FileStream(dstFile, FileMode.Create, FileAccess.Write); BinaryWriter BW = new BinaryWriter(FS, Encoding.UTF8); //Az archiválandó mappák listája Queue<string> dirsToBackUp = new Queue<string>(); dirsToBackUp.Enqueue(srcPath); //Kiinduló könyvtár hozzáadása string path, relPath; //A teljes és a relatív elérési út string[] files, dirs; //Az adott mappában található fájlok és könyvtárak //Az eredeti elérési út hossza; mivel minden könyvtár és fájl ez alatt van, //ezért azok elérési útjának ezen a hosszon túlnyúló része //adja a relatív elérési utat. int sourcePathLength = srcPath.Length; while (dirsToBackUp.Count > 0) //Amíg van mit másolni path = dirsToBackUp.Dequeue(); //Elem kiolvasása a sorból relPath = path.Remove(0, sourcePathLength); //Relatív elérési út számítása BW.Write((byte)ArchiveBlockType.Directory); //Könyvtár blokk BW.Write(relPath); //Könyvtár relatív helye //Az adott könyvtárban található fájlok lekérdezése files = Directory.GetFiles(path, fileFilter); for (int i = 0; i < files.Length; i++) try //Lemásoljuk a fájlokat //Fájl blokk BW.Write((byte)ArchiveBlockType.File); //Fájl eléri útja BW.Write(files[i].Remove(0, sourcePathLength)); … Könyvtárfa archiválása BackUpTools (+)
Könyvtárfa archiválása … //Adat blokk BW.Write((byte)ArchiveBlockType.Data); //Adatblokk írása WriteFileToStream(FS, files[i]); } catch { Console.WriteLine("Cannot read file at " + files[i]); //Az adott könyvtárban található könyvtárak lekérdezése dirs = Directory.GetDirectories(path); for (int i = 0; i < dirs.Length; i++) //Hozzáadás a másolandó listához dirsToBackUp.Enqueue(dirs[i]); Console.WriteLine("Cannot read directory at " + path); BW.Close(); //Fájl bezárása Console.WriteLine("Cannot create archive."); Könyvtárfa archiválása BackUpTools (+)
Könyvtárfa archiválása //Könyvtárfa kibontása fájlból public static void Extract(string srcFile, string dstPath) { //Fájl megnyitása FileStream FS = new FileStream(srcFile, FileMode.Open, FileAccess.Read); BinaryReader BR = new BinaryReader(FS, Encoding.UTF8); string currentDir = ""; string currentFile = ""; int dataType; while (FS.Position < FS.Length) //Amíg nem érünk a fájl végére //Blokktípus kiolvasása dataType = BR.ReadByte(); switch (dataType) case 0: //Directory currentDir = BR.ReadString(); Directory.CreateDirectory(dstPath + '\\' + currentDir); break; case 1: //File currentFile = BR.ReadString(); case 2: //Data ReadStreamToFile(FS, dstPath + '\\' + currentFile); } BR.Close(); //Fájl bezárása //Használat //Archive(@"F:\Axodox\Dokumentumok\Visual Studio 2008\Projects\Minesweeper”, @”backup.arc", "*.*"); //Extract(@"backup.arc", @"C:\Users\Major Péter\Desktop\ZZZ"); BackUpTools (+)
Érdekesség Mivel az I/O műveletek időigényesek, megszakításuk is szükséges lehet, ezért gyakorlatban ezeket általában párhuzamos programozással, a program többi részétől külön szálon végzik. Így a program nagy fájlok mentése esetén is reszponzív marad a felhasználó parancsaira. Az érdeklődőknek ajánlom a System.ComponentModel.BackgroundWorker osztály által nyújtott lehetőségek megismerését, mellyel - egyszerűbb esetekben - a többszálú programozás veszélyek nélkül megoldható. (Profibbak próbálkozhatnak a System.Threading névtérrel is :)