1 Cel ćwiczenia
1
POLITECHNIKA ŚLĄSKA
w Gliwicach
WYDZIAŁ INŻYNIERII BIOMEDYCZNEJ
LABORATORIUM
JĘZYKÓW PROGRAMOWANIA
C# – obsługa plików
Opracował: mgr inż. Marcin Rudzki
v.3.00, Gliwice 2010
1
Cel ćwiczenia
Celem ćwiczenia jest zapoznanie studentów z podstawowymi metodami obsługi plików w języku C#. Studenci
zapoznają się ze sposobem użycia klas StreamReader i StreamWriter służącymi do obsługi plików tekstowych
oraz BinaryReader i BinaryWriter służącymi do obsługi plików binarnych.
UWAGA! Od studentów wymaga się, aby przystępując do niniejszego ćwiczenia potrafili samodzielnie obsłu-
giwać środowisko programistyczne Visual Studio w zakresie umożliwiającym pisanie programu, kompilowanie
i uruchamianie napisanego programu oraz jego debugowanie.
2
Materiał obowiązkowy
Wiedza i umiejętności dotyczące materiału zaprezentowanego w tym rozdziale mogą zostać sprawdzone przed la-
boratorium w ramach krótkiego zadania sprawdzającego (pisemnego lub na komputerach).
Omawiane klasy służące do zapisu i odczytu plików tekstowych oraz binarnych, tj. StreamReader, StreamWri-
ter, BinaryReader, BinaryWriter, są zdefiniowane wewnątrz przestrzeni nazw System.IO. Dlatego, aby się
nimi w łatwy sposób posługiwać, należy na początku kodu programu umieścić linię using System.IO;
Zapamiętaj! using System.IO;
2.1
Zapis i odczyt plików tekstowych
Do zapisu plików tekstowych służy klasa StreamWriter. Tworzenie obiektu tej klasy przebiega standardowo –
za pomocą operatora new połączonego z wywołaniem jednego z wielu możliwych konstruktorów
. Najprostszym
wywołaniem konstruktora jest przekazanie mu pełnej ścieżki dostępu do pliku, który chcemy utworzyć. Takie
wywołanie konstruktora spowoduje, że jeśli plik o takiej nazwie już istnieje, to zostanie on nadpisany, np.:
1
StreamWriter sWriter = new StreamWriter(@"c:\plik.txt");
1
Konstruktory są opisane w osobnej instrukcji laboratoryjnej.
2.1
Zapis i odczyt plików tekstowych
2
Aby nie nadpisywać poprzedniej zawartości pliku, tylko dopisywać nowe dane należy wywołać konstruktor
z dodatkowym argumentem informującym czy do pliku dane mają być dopisywane (true) czy też poprzednia
zawartość pliku ma być zastąpiona nowymi danymi (false).
1
StreamWriter sWriter = new StreamWriter(@"c:\plik.txt", true);
Zapamiętaj! StreamWriter sWriter = new StreamWriter(nazwaPliku);
Mając już otwarty plik do zapisu, można do niego zapisywać dowolny tekst praktycznie tak samo jak na ekran
– używając metody Write() lub WriteLine(). Przykład zapisu jednej linii tekstu pokazano poniżej:
1
sWriter.WriteLine("Ten tekst zostanie zapisany do pliku.");
Zapamiętaj! sWriter.WriteLine(); sWriter.Write();
Po zakończeniu operacji zapisu należy upewnić się, że wszystkie dane z bufora są zapisane do pliku oraz za-
mknąć plik. Tym celom służą metody Flush() oraz Close(). Niewywołanie metody Close() spowoduje,
że w utworzonym pliku nie będzie widać wpisanej zawartości oraz może on być uznany przez system opera-
cyjny jako w użyciu, tym samym niemożliwa może być jego modyfikacja bądź usunięcie.
Zapamiętaj! sWriter.Close();
Chcąc zapisać dowolne dane (np. liczby), ale w sformatowanej postaci tekstowej, wystarczy wywołać me-
todę Write() lub WriteLine() podobnie jak dla klasy Console przy wyświetlaniu danych na ekran, np.:
sWriter.WriteLine("dana: {0} opis: {1}", zmiennaLiczbowa, zmiennaTekstowa);
Zbierając przedstawione do tej pory fragmenty kodu w jedną całość, otrzymamy najprostszy program zapi-
sujący plik tekstowy.
1
string nazwaPliku = "plik.txt";
2
string napis = "jakis napis";
3
double liczba = 12345.6789;
4
char znak = ’q’;
5
6
StreamWriter sWriter;
7
sWriter = new StreamWriter(nazwaPliku);
8
sWriter.WriteLine(napis);
9
sWriter.WriteLine(liczba);
10
sWriter.WriteLine(znak);
11
sWriter.WriteLine("{0} {1:F3} {2}", napis, liczba, znak);
12
Console.WriteLine("Do pliku {0} zapisano dane: {1} {2} {3}", nazwaPliku, napis, liczba, znak);
13
sWriter.Flush();
14
sWriter.Close();
Jeśli nie zostanie podana pełna ścieżka dostępu do pliku, to plik jest tworzony w katalogu roboczym pro-
gramu. W przypadku uruchomiania projektu wprost ze środowiska Visual Studio, plik zostanie stworzony w ka-
talogu [Visual Studio projects]/[Nazwa Projektu]/bin/Debug/ lub .../bin/Release/.
2.1
Zapis i odczyt plików tekstowych
3
Mając zapisane w pliku tekstowym dane, należałoby być w stanie je odczytać. W tym celu można użyć klasy
StreamReader, która umożliwia odczyt plików tekstowych. Sposób użycia jest praktycznie identyczny, jak klasy
StreamWriter.
Zapamiętaj! StreamReader sReader = new StreamReader(nazwaPliku);
Aby odczytać uprzednio zapisane dane można posłużyć się poniższym kodem:
1
string nazwaPliku = "plik.txt";
2
string napis;
3
double liczba;
4
char znak;
5
6
StreamReader sReader;
7
sReader = new StreamReader(nazwaPliku);
8
napis = sReader.ReadLine();
9
liczba = double.Parse(sReader.ReadLine());
10
znak = char.Parse(sReader.ReadLine());
11
Console.WriteLine("Z pliku {0} odczytano dane: {1} {2} {3}", nazwaPliku, napis, liczba, znak);
12
sReader.Close();
Poniższy listing prezentuje przykładowy sposób zapisu tablicy liczb do pliku tekstowego oraz jej ponowny
odczyt.
1
class Program
2
{
3
static void ZapiszTablice(string nazwaPliku, double[] tablica)
4
{
5
StreamWriter sWriter = new StreamWriter(nazwaPliku);
6
sWriter.WriteLine(tablica.Length); // najpierw zapisac ilosc elementow
7
foreach (double d in tablica) sWriter.WriteLine(d); // potem kazdy element w nowej linii
8
Console.WriteLine("Zapisano do pliku {0}", nazwaPliku);
9
sWriter.Flush();
10
sWriter.Close();
11
}
12
13
static void OdczytajTablice(string nazwaPliku, out double[] tablica)
14
{
15
tablica = null;
16
StreamReader sReader = new StreamReader(nazwaPliku);
17
int ile = int.Parse(sReader.ReadLine()); // najpierw byla zapisana dlugosc tablicy
18
tablica = new double[ile];
19
for (int i = 0; i < ile; i++)
20
tablica[i] = double.Parse(sReader.ReadLine()); // a potem jej elementy
21
Console.WriteLine("Odczytano z pliku {0}", nazwaPliku);
22
sReader.Close();
23
}
24
25
static void Main(string[] args)
26
{
27
Random rnd = new Random();
28
double[] tab = new double[10];
29
for (int i = 0; i < tab.Length; i++) tab[i] = rnd.NextDouble();
30
3 Materiał dodatkowy
4
31
Console.WriteLine("Zawartosc tablicy:");
32
foreach (double d in tab) Console.WriteLine("{0:F5}", d);
33
34
ZapiszTablice("tablica.txt", tab);
35
36
Array.Clear(tab, 0, tab.Length);
37
38
Console.WriteLine("Zawartosc tablicy:");
39
foreach (double d in tab) Console.WriteLine("{0:F5}", d);
40
41
OdczytajTablice("tablica.txt", out tab);
42
Console.WriteLine("Zawartosc tablicy:");
43
foreach (double d in tab) Console.WriteLine("{0:F5}", d);
44
45
Console.ReadKey();
46
}
47
}
Zaleca się samodzielne przestudiowanie powyższego przykładu oraz przetestowanie go w praktyce.
Przy obsłudze plików tekstowych przydatna jest metoda Split() umożliwiająca dzielenie łańcucha na pod-
łańcuchy. Przerobienie powyższego przykładu tak, aby można było zapisywać i odczytywać tablice dwuwymia-
rowe, przy czym każdy wiersz tablicy ma być zapisany jako wiersz w pliku tekstowym, pozostawia się jako
zadanie do samodzielnego wykonania.
3
Materiał dodatkowy
3.1
Klasa FileStream
Klasa FileStream umożliwia niskopoziomowy dostęp (zapis i odczyt) do plików wszystkich typów. Niskopozio-
mowy czyli na poziomie pojedynczych bajtów. Za pomocą tej klasy można odczytywać i zapisywać wyłącznie
pojedyncze bajty bądź też bloki bajtów bez jakiejkolwiek ich interpretacji. Z tego powodu operowanie tekstami,
wartościami zmiennych wielobajtowych (jak np. double) jest utrudnione i musiałoby być implementowane „od
zera”. Jednak do obsługi plików przechowujących tylko wartości zapisane w zmiennej 8-mio bitowej nadaje
się doskonale. Klasa FileStream jest podstawą, której używają klasy obsługujące różne typy danych takie,
jak poznane już StreamReader i StreamWriter oraz przedstawione poniżej BinaryReader i BinaryWriter.
Konstruktory tych klas można także wywołać z argumentem będącym obiektem klasy FileStream.
Przykład użycia klasy FileStream do zapisu i odczytu pliku binarnego przedstawiono poniżej.
1
string nazwaPliku = @"plik.bin";
2
byte liczba;
3
4
FileStream fStream = new FileStream(nazwaPliku, FileMode.Create);
5
6
Console.Write("Ile liczb chcesz podac? ");
7
int ile = int.Parse(Console.ReadLine());
8
9
for ( ; ile > 0; ile--)
10
{
11
Console.Write("Podaj liczbe [0..255]: ");
12
liczba = byte.Parse(Console.ReadLine());
13
fStream.WriteByte(liczba);
3.2
Obsługa dowolnych plików binarnych – zapis i odczyt
5
14
// FileStream.WriteByte() potrafi zapisywac TYLKO bajty (liczby 8-mio bitowe)
15
}
16
fStream.Flush();
17
fStream.Close();
18
Console.WriteLine("Zapisano do pliku {0}", nazwaPliku);
19
20
fStream = new FileStream(nazwaPliku, FileMode.Open);
21
22
ile = fStream.Length;
23
24
for (int i = 0; i < ile; i++ )
25
{
26
liczba = fStream.ReadByte();
27
Console.Write(liczba + " ");
28
}
29
fStream.Close();
30
31
Console.WriteLine("\nOdczytano z pliku {0}", nazwaPliku);
32
Console.ReadKey();
Jak widać użycie klasy FileStream wygląda praktycznie tak samo, jak poznanych już klas obsługujących
pliki tekstowe. Po udanym otwarciu pliku w trybie do zapisu (new FileStream(nazwaPliku,FileMode.Cre-
ate)) następuje zapis pojedynczych bajtów – liczb podanych przez użytkownika (fStream.WriteByte(liczba)).
Na koniec następuje synchronizacja bufora (fStream.Flush()) oraz zamknięcie strumienia (fStream.Close()).
Odczyt pliku wygląda podobnie. Po otwarciu pliku do odczytu (new FileStream(nazwaPliku,FileMode.
Open)) następuje odczyt (liczba=fStream.ReadByte()) tylu bajtów, jaka jest długość pliku-strumienia
(fStream.Length) oraz ich wyświetlenie na ekranie. Na koniec za pomocą fStream.Close() następuje za-
mknięcie pliku.
3.2
Obsługa dowolnych plików binarnych – zapis i odczyt
Jak już wspomniano, za pomocą klasy FileStream możliwy jest odczyt oraz zapis dowolnych plików, ale na po-
ziomie pojedynczych bajtów. Aby w łatwy sposób obsługiwać pliki binarne przechowujące wartości dowolnych
typów (np. int czy double), należy użyć klas BinaryReader oraz BinaryWriter. Stanowią one rozszerzenie
możliwości klasy FileStream.
Poniższe przykłady pokazują, w jaki sposób zapisywać wartości typu int do pliku binarnego oraz jak później
je odczytać.
1
string nazwaPliku = @"plik.bin";
2
int liczba;
3
int ile;
4
5
FileStream fStream = new FileStream(nazwaPliku,FileMode.Create);
6
BinaryWriter bWriter = new BinaryWriter(fStream);
7
8
Console.Write("Ile liczb chcesz podac? ");
9
ile = int.Parse(Console.ReadLine());
10
for ( ; ile > 0; ile--)
11
{
12
Console.Write("Podaj liczbe calkowita: ");
13
liczba = int.Parse(Console.ReadLine());
14
bWriter.Write(liczba);
3.2
Obsługa dowolnych plików binarnych – zapis i odczyt
6
15
}
16
bWriter.Flush();
17
fStream.Flush();
18
bWriter.Close();
19
fStream.Close();
20
21
Console.WriteLine("Zapisano do pliku {0}", nazwaPliku);
22
Console.ReadKey();
Jak łatwo zauważyć, potrzebne są obie klasy, tj. FileStream dająca dostęp do pliku oraz BinaryWriter
umożliwiająca operowanie danymi innych typów, niż tylko bajty. Zapis pliku odbywa tak samo, jak poprzed-
nio, czyli po otwarciu pliku (tym razem tworząc obiekty dwóch klas) następuje zapis wartości za pomocą
bWriter.Write(). Po skończonym zapisie bufory zapisu są synchronizowane oraz następuje zamknięcie pliku.
Odczyt tak stworzonego pliku pokazano poniżej.
1
string nazwaPliku = @"plik.bin";
2
int liczba;
3
4
FileStream fStream = new FileStream(nazwaPliku, FileMode.Open);
5
BinaryReader bReader = new BinaryReader(fStream);
6
7
while (bReader.PeekChar() != -1)
8
{
9
liczba = bReader.ReadInt32();
10
Console.WriteLine(liczba);
11
}
12
bReader.Close();
13
fStream.Close();
14
15
Console.WriteLine("Odczytano z pliku {0}", nazwaPliku);
16
Console.ReadKey();
Wszystko przebiega podobnie jak w poprzednich przykładach, tylko do sprawdzenia, czy wystąpił ko-
niec pliku użyto metody PeekChar(). Zwraca ona kolejny bajt ze strumienia, lecz nie pobiera go (tzn. nie
zostaje zwiększony wskaźnik pliku). Do określenia ile liczb należy odczytać z pliku nie można użyć bezpo-
średnio fStream.Length gdyż jest to długość pliku w bajtach, a nie w wielokrotności liczb typu int. Klasa
BinaryReader oferuje wiele metod odczytujących dane, które zebrane zostały w Tab.
. Pamiętać należy, że
dowolny ciąg bajtów może być interpretowany w różny sposób. To od programisty zależy jak mają zostać in-
terpretowane dane z pliku binarnego. Formalnie nic nie stoi na przeszkodzie, by plik zapisany wartościami typu
double odczytać metodą ReadChar(), jednak tak odczytane dane z punktu widzenia użytkownika są błędne.
4 Zadania do wykonania przed ćwiczeniem
7
metoda
opis
PeekChar()
Zwraca kolejny znak ze strumienia ale nie pobiera go.
Read()
Pobiera bajt (lub bajty) ze strumienia.
ReadBoolean()
Pobiera 1-no bajtową wartość logiczną.
ReadByte()
Pobiera 1-no bajtową wartość stałoprzecinkową bez znaku.
ReadBytes()
Pobiera określoną liczbę 1-no bajtowych wartości stałoprzecinkowych bez znaku.
ReadChar()
Pobiera pojedynczą wartość znakową, zależnie od kodowania może to być 1 lub 2 bajty.
ReadChars()
Pobiera określoną liczbę wartości znakowych, uwaga jak powyżej.
ReadDecimal()
Pobiera 16-to bajtową wartość numeryczną.
ReadDouble()
Pobiera 8-mio bajtową wartość zmiennoprzecinkową.
ReadInt16()
Pobiera 2-u bajtową wartość całkowitoliczbową ze znakiem.
ReadInt32()
Pobiera 4-ro bajtową wartość całkowitoliczbową ze znakiem.
ReadInt64()
Pobiera 8-mio bajtową wartość całkowitoliczbową ze znakiem.
ReadSByte()
Pobiera 1-no bajtową wartość całkowitoliczbową ze znakiem.
ReadSingle()
Pobiera 4-ro bajtową wartość zmiennoprzecinkową.
ReadString()
Pobiera ciąg znaków.
ReadUInt16()
Pobiera 2-u bajtową wartość całkowitoliczbową bez znaku.
ReadUInt32()
Pobiera 4-ro bajtową wartość całkowitoliczbową bez znaku.
ReadUInt64()
Pobiera 8-mio bajtową wartość całkowitoliczbową bez znaku.
Tab. 1: Metody klasy BinaryReader służące do odczytu danych
4
Zadania do wykonania przed ćwiczeniem
Zaleca się wykonanie poniższych ćwiczeń przed przystąpieniem do ćwiczenia laboratoryjnego:
1. Napisać program, który w pliku tekstowym zapisze dane o osobie, takie jak: imię, nazwisko, dzień, miesiąc
i rok urodzenia.
2. Zaimplementować funkcje służące do zapisu i odczytu tablic dwuwymiarowych.
3. Za pomocą wyżej zaimplementowanych funkcji wczytać z plików dwie macierze dwuwymiarowe, jeśli to
możliwe wykonać ich dodawanie, wynik zapisać w trzecim pliku tekstowym.
5
Program ćwiczenia
W ramach ćwiczenia laboratoryjnego studenci mają za zadanie napisać programy zadane przez prowadzącego.
Zakres tematyczny pokrywa się z materiałem przedstawionym w niniejszej instrukcji. Zalecane jest, aby studenci
przed laboratorium przećwiczyli we własnym zakresie zaprezentowaną część materiału.
Przed rozpoczęciem laboratorium przewidziane jest sprawdzenie wiedzy i umiejętności
studentów z nastę-
pujących zagadnień:
1. zapis i odczyt plików tekstowych, uwzględniając konwersję odczytanych danych na poprawne typy,
2. zapis i odczyt plików binarnych (*).
2
czyli tzw. wejściówka, której niezaliczenie powoduje niezaliczenie całego ćwiczenia laboratoryjnego
LITERATURA
8
Literatura
[1] Hejlsberg A., Wiltamuth S., Golde P.; Microsoft C# Language Specification; Addison-Wesley; 2004.
[2] The C# Language;
http://msdn.microsoft.com/en-us/vcsharp/aa336809.aspx
[3] Liberty J. (Tłumaczenie: Walczak T.); C# Programowanie; Helion; 2005.
[4] Lis M.; C# Praktyczny kurs; Helion; 2007.
[5] Portal CentrumXP.pl, kurs C# cz. I oraz cz. II;
http://www.centrumxp.pl/dotNET/
[6] Dusiński D; Kurs C# (ver 2.0);
http://www.toya.net.pl/~daniel_d/csharp/index.html
[7]