Dostęp do pliku
Przegląd zagadnień
Umiemy już sprawnie manipulować obiektami przechowywanymi w pamięci
operacyjnej. Często konieczne jest jednak, przechowanie danych w pamięci
dyskowej, aby móc je wykorzystać przy następnym uruchomieniu programu.
W języku C# większość klas pomocnych przy korzystaniu z pamięci dyskowej
znajduje się w przestrzeni nazw System.IO.
Po zakończeniu tego rozdziału student powinien wiedzieć i potrafić:
Co to jest plik.
Korzystać z pliku przy pomocy strumieni.
Dokonywać operacji na strukturze systemu plików
Definicja pliku
Plik jest to skończony zbiór danych (informacji) przechowywana na dysku,
dostępna za pomocą nazwy rozszerzonej o ścieżkę dostępu, który stanowi dla
systemy operacyjnego całość.
Zanim skorzystamy z pliku musimy go otworzyć. Otwarcie pliku powoduje
utworzenie uchwytu do pliku. Podczas otwarcia musimy określić, w jaki sposób
będziemy z niego korzystać. Uzyskujemy to przez ustawienie pewnych
atrybutów, przy pomocy typów wyliczeniowych zdefiniowanych w przestrzeni
nazw System.IO:
System.IO.FileAccess - definiuje tryb dostępu do pliku
o FileAccess.Read - dane mogą być czytane z pliku
o FileAccess.Write - dane mogą być zapisywane do pliku
o FileAccess.ReadWrite - dane mogą być czytane z i zapisywane do
pliku
System.IO.FileShare - definiuje zasady współużytkowania pliku z
innymi programami (procesami):
o FileShare.Delete
pozwala inny programom skasować plik
o FileShare.Inheritable - uchwyt pliku jest dostępny dla
procesów potomnych
o FileShare.Read - pozwala otworzyć powtórnie plik, ale tylko do
zapisu
o FileShare.ReadWrite - pozwala powtórnie otworzyć plik do
zapisu lub odczytu
o FileShare.Write - pozwala powtórnie otworzyć plik, ale tylko do
odczytu
System.IO.FileMode - definiuje sposób otwarcia lub utworzenia
pliku
o FileMode.Append - otwiera plik w celu dopisywania do pliku, jeżeli
żądany plik nie istnieje jest tworzony. Atrybut ten może pracować tylko
w połączeniu z atrybutem FileAccess.Write.
o FileMode.Create - otwiera plik do zapisu. W przypadku, gdy plik
nie istnieje jest tworzony, jeżeli istnieje jest nadpisywany.
o FileMode.CreateNew - otwiera plik do zapisu. W przypadku, gdy
plik nie istnieje jest tworzony, jeżeli istnieje rzucany jest wyjątek.
o FileMode.Open - otwiera plik do odczytu lub zapisu w zależności od
atrybutu FileAccess. Jeżeli plik nie istnieje rzucany jest wyjątek.
o FileMode.OpenOrCreate - otwiera plik do odczytu lub zapisu w
zależności od atrybutu FileAccess. Otwiera istniejący plik, jeżeli plik nie
istnieje jest tworzony.
o FileMode.Truncate - otwiera plik do zapisu. Otwiera istniejący
plik kasując jego zawartość
Strumienie - definicja
W celu skorzystania z pliku, musimy skojarzyć z nim pewien obiekt zwany
strumienie.
Strumień jest to pewna warstwa abstrakcyjna, która umożliwia nam zapis i
odczyt danych, nie tylko z pliku, ale z różnych źródeł. Na strumieniu mamy
zdefiniowane pewne operacje, chociaż ich dostępność zależy od typu
strumienia i sposobu jego otwarcia. Do operacji zdefiniowanych na strumieniu
należą:
czytanie danych (reading) - pobieranie danych ze strumienia i umieszczanie
ich w pewnej strukturze danych
zapis danych (writting) - wstawienie danych do strumienia z pewnej
struktury danych
ustawienie bieżącej pozycji w strumieniu (seeking)
Strumienie w bibliotece .Net Framework
W bibliotece .Net istnieje szereg różnych klas reprezentujących strumienie.
Klasy te możemy podzielić na trzy grupy.
Strumienie bezpośrednio podłączone do źródła:
System.IO.FileStream, System.IO.MemoryStream,
System.Net.Sockets.NetworkStream
Strumienie pośredniczące. Strumienie te mogą pośredniczyć w komunikacji
z wyżej wymienionymi strumieniami:
System.Security.Cryptography.CryptoStream,
System.IO.BufferedStream
Klasy używane do odczytu/zapisu z/do strumienia
o System.IO.BinaryReader, System.IO.BinaryWriter
- odczyt lub zapis danych binarnych
o System.IO.StreamReader, System.IO.StreamWriter -
odczyt lub zapis danych tekstowych (napisów)
Poszczególne grupy udostępniają nam różny stopień abstrakcji przesyłania
danych do strumienia. Grupa pierwsza udostępnia najprostszy interfejs, w
którym do strumienia można zapisywać oraz z niego pobierać tylko pojedyncze
bajty. Grupa druga pozwala na dodanie szyfrowania lub buforowania
strumienia. Przy pomocy obiektów klas grupy trzeciej możemy wymieniać ze
strumieniem informacje w formie napisów, liczb i innych obiektów.
Operacje na pliku - przykład
W celu skorzystania z pliku należy wykonać następujące czynności:
1. Utwórz obiekt klasy FileStream skojarzony z odpowiednim plikiem:
a. Plik do odczytu:
FileStream zrodlo = new FileStream(
nazwaZrodlo, FileMode.Open,
FileAccess.Read, FileShare.Read);
b. Plik do zapisu:
FileStream cel = new FileStream(
nazwaCelu, FileMode.Create,
FileAccess.Write, FileShare.None);
2. Wykonaj żądane operacje na pliku:
a. Odczyt danych z pliku
i. Wczytywanie pojedynczego bajt-u - metoda ReadByte:
int i = zrodlo.ReadByte();
Przekazanie przez metodę wartości -1 oznacza osiągnięcie końca
pliku.
ii. Wczytywanie bloku bajtów do tablicy
byte [] bufor = new byte[liczbaBajtow];
int iloscWczytanychBajtow =
zrodlo.Read(bufor, indeks, liczbaBajtow);
Parametr indeks oznacza który element tablicy zostanie nadpisany
jako pierwszy. Mniejsza wartość zmiennej
ilośćWczytanychBajtow od parametru liczbaBajtow -
ile bajtów należało wczytać, oznacza osiągnięcie końca pliku
b. Zapis danych do pliku
i. Zapis pojedynczego bajt-u - metoda WriteByte:
cel.WrteByte(wartosc);
ii. Zapis bloku bajtów z tablicy
byte [] bufor = new byte[liczbaBajtow];
//nadanie wartości elementom tablicy
cel.Read(bufor, indeks, liczbaBajtow);
Parametr indeks oznacza który element tablicy zostanie zapisany
jako pierwszy.
3. Zamknij plik
zrodlo.Close();
cel.Close();
Ponieważ operacje plikowe mogą generować liczne wyjątki np.: brak
odpowiednich uprawnień do pliku, brak żądanego pliku, brak miejsca na dysku,
niedostępny dysk sieciowy itp., powinny być umieszczone w bloku try:
FileStream zrodlo = null, cel = null;
try{
//operacje na pliku
}
catch (IOException ex){
Console.WriteLine("Problemy z plikiem.\n{0}",
ex.Message);
}
finally{
if (zrodlo != null)
zrodlo.Close();
if (cel != null)
cel.Close();
}
Jeżeli chcemy zapisywać do i odczytywać z pliku wartości konkretnych typów
wbudowanych, możemy skorzystać z klas BinaryWriter i
BinaryReader.
Do obsługi plików tekstowych możemy skorzystać z klas StreamReader i
StreamWriter.
Przykłady użycia klas FileStream, BinaryWriter i BinaryReader
oraz StreamReader i StreamWriter można znaleźć w programach:
KopiowaniePlikow, PlikiBinarne, PlikiTekstowe
, które,
stanowią część rozwiązania Kurs\Demo\Modul14\Modul14.sln, gdzie
Kurs jest katalogiem, gdzie skopiowano pliki kursu.
Trzeba pamiętać, że przestrzeń nazw System.IO nie jest domyślnie
importowana. Jeżeli nie chcemy stosować w pełni kwalifikowanych nazw, sami
powinniśmy ją zaimportować.
Operacje na strukturze systemu plików
W bibliotece .Net Framework istnieją również klasy, które umożliwiają nam
operacje na strukturze systemu plików np. sprawdzenie czy istnieje żądany plik,
sprawdzenie wielkości pliku, wylistowanie zawartości katalogu, utworzenie
katalogu itp.
Directory - służy do bezpośrednich operacji na plikach i katalogach
File - udostępnia metody do operacji na plikach
Path - zawiera operacje na tekście zawierającym informacje o ścieżce
dostępu do pliku lub katalogu
FileSystemWatcher - ustawienie kontroli na pliku lub katalogu
Uwaga:
W przypadku gdy będziemy wykonywać kilka operacji na pojedynczym pliku
lub katalogu zamiast korzystać z klas File oraz Directory, powinniśmy
skorzystać z obiektów klas FileInfo oraz DirectoryInfo.
Pytania sprawdzające
1. Co to jest plik?
Odp.
Plik jest to skończony zbiór danych (informacji) przechowywana na dysku,
dostępna za pomocą nazwy rozszerzonej o ścieżkę dostępu, który stanowi
dla systemy operacyjnego całość.
2. Co to jest strumień?
Odp.
Strumień jest to pewna warstwa abstrakcyjna, która umożliwia nam zapis i
odczyt danych z różnych źródeł.
3. Obiektów jakich klas użyjesz do współpracy z plikami tekstowymi?
Odp.
Do współpracy z plikami tekstowymi użyjemy odpowiednio, do odczytu
obiektu klasy StreamReader, do zapisu obiektu StreamWriter.
Laboratorium
Ćwiczenie 1:
Napisz program szyfrujący plik. Pogram działa w następujący sposób.
Pobierz nazwę pliku do szyfrowania.
Pobierz nazwę pliku zaszyfrowanego.
Pobierz wartość klucza (maski) szyfrującego.
Wczytuj po jednym bajcie z pliku źródłowego, następnie wykonaj
operacje xor na kluczu i wartości wczytanej. Wynik wyrażenia zapisz
do pliku zaszyfrowanego. Czynność powtażaj do osiągnięcia koońca
pliku do szyfrowania.
Warto zauważyć że podając jako plik do szyfrowania plik wcześniej
zaszyfrowany i klucz który został użyty do szyfrowania tego pliku
spowodujemy jego rozszyfrowanie.
1. Uruchom Visual Studio
Naciśnij przycisk Start systemu Windows, wybierz Wszystkie Programy
następnie Microsoft Visual Studio 2005/ Microsoft Visual Studio 2005.
2. Utwórz nowy projekt
a. Z menu File wybierz New/Project...
b. W oknie dialogowym New Project określ następujące właściwości:
i. typu projektu: Visual C#/Windows
ii. szablon: Console Application
iii. lokalizacja: Moje Dokumenty\Visual Studio 2005\Projects\
iv. nazwa projektu: Koder.
v. nazwa rozwiązania: Lab14a
3. Zaimportuj przestrzeń nazw System.IO.
using System.IO;
4. W metodzie Main wykonaj instrukcje zgodnie z poleceniem do tego
ćwiczenia:
static void Main(string[] args)
{
Console.Write("Podaj nazwę pliku do
¬zaszyfrowania: ");
string nazwaZrodla = Console.ReadLine();
Console.Write("Podaj nazwę pliku zaszyfrowanego:
¬");
string nazwaCelu = Console.ReadLine();
Console.Write("Podaj wartość klucza (od 0 do
¬255): ");
byte klucz=Convert.ToByte(Console.ReadLine());
FileStream zrodlo = null, cel = null;
try
{
zrodlo = new FileStream(nazwaZrodla,
FileMode.Open);
cel = new FileStream(nazwaCelu,
FileMode.Create);
int i = zrodlo.ReadByte();
while(i != -1)
{
cel.WriteByte((byte)(i ^ klucz));
i = zrodlo.ReadByte();
}
}
finally
{
if (zrodlo != null)
zrodlo.Close();
if (cel != null)
cel.Close();
}
Console.WriteLine("Operacja szyfrowania
¬zakończona.");
Console.ReadKey();
}
5. Skompiluj i uruchom program.
Ćwiczenie 2:
Napisz program, który będzie zawierał implementację bazy kontaktów. W tym
ćwiczeniu będziemy rozszerzać program napisany w pierwszym ćwiczeniu
laboratorium do rozdziału dwunastego. Rozszerzenie będzie polegało na:
dodaniu do klasy Data metody zapisującej podaną datę do podanego
pliku
dodaniu do klasy Data metody wczytującej datę z podanego pliku
dodaniu do klasy Osoba metody zapisującej podaną osobę do podanego
pliku
dodaniu do klasy Osoba metody wczytującej osobę z podanego pliku
dodaniu do klasy List metody zapisującej wszystkie osoby z listy do
podanego pliku
dodaniu do klasy List metody wczytującej wszystkie osoby z podanego
pliku do danej listy
w programie głównym dodanie do menu pozycji zapisz bazę oraz
wczytaj bazę i odpowiednie ich oprogramowanie
1. Skopiuj katalog Kurs\Lab\Modul14\Start\Lab14b, gdzie Kurs
jest katalogiem, gdzie skopiowano pliki kursu.
2. Otwórz skopiowane rozwiązanie Lab14b.sln
3. Otwórz plik Biblioteka.cs z projektu Biblioteka i dodaj do niego
następujący kod
a. Zaimportuj przestrzeń nazw System.IO
using System.IO;
b. Do klasy Data dodaj metodę ZapiszDate, która przyjmuje jako
parametry datę do zapisania w pliku oraz obiekt klasy
BinaryWriter przy pomocy którego dokonamy zapisu
public static void ZapiszDate(Data d,
BinaryWriter bw)
{
bw.Write(d.Rok);
bw.Write(d.Miesiac);
bw.Write(d.Dzien);
}
c. Do klasy Data dodaj metodę WczytajDate, która przyjmuje jako
argumenty datę do wczytania przesyłaną jako parametr wyjściowy oraz
obiekt klasy BinaryReader przy pomocy którego dokonamy
wczytania daty
public static void WczytajDate(out Data d,
BinaryReader br)
{
d.Rok = br.ReadInt32();
d.Miesiac = br.ReadByte();
d.Dzien = br.ReadByte();
}
d. Do klasy Osoba dodaj metodę ZapiszOsobe, która przyjmuje jako
parametry osobę do zapisania w pliku oraz obiekt klasy
BinaryWriter przy pomocy którego dokonamy zapisu
public static void ZapiszOsobe(Osoba os,
BinaryWriter bw)
{
bw.Write(os.Imie);
bw.Write(os.Nazwisko);
Data.ZapiszDate(os.DataUrodzenia, bw);}
e. Do klasy Osoba dodaj metodę WczytajOsobe, która przyjmuje
jako argumenty osobę do wczytania przesyłaną jako parametr
wyjściowy oraz obiekt klasy BinaryReader przy pomocy którego
dokonamy wczytania osoby
public static void WczytajOsobe(out Osoba os,
BinaryReader br)
{
os = new Osoba();
os.Imie = br.ReadString();
os.Nazwisko = br.ReadString();
Data.WczytajDate(out os.DataUrodzenia, br);
}
4. Otwórz plik List.cs z projektu Listy i dodaj do niego następujący kod
a. Zaimportuj przestrzeń nazw System.IO
using System.IO;
b. Do klasy List dodaj metodę ZapiszListe, która przyjmuje jako
parametry listę do zapisania w pliku oraz obiekt klasy
BinaryWriter przy pomocy którego dokonamy zapisu. Jako
pierwszy parametr zapisz ilość elementów listy
public static void ZapiszListe(List mojaLista,
BinaryWriter bw)
{
bw.Write(List.GetCount(mojaLista));
Node tmp = mojaLista.Head;
while (tmp != null)
{
Osoba.ZapiszOsobe(tmp.Data, bw);
tmp = tmp.Next;
}
}
c. Do klasy List dodaj metodę WczytajListe, która przyjmuje jako
argumenty listę do wczytania przesyłaną jako parametr wyjściowy oraz
obiekt klasy BinaryReader przy pomocy którego dokonamy
wczytania listy. Jako pierwszy parametr wczytaj liczbę elementów listy
public static void WczytajListe(
out List mojaLista, BinaryReader br)
{
mojaLista = new List();
int n = br.ReadInt32();
Osoba tmp;
for (int i = 0; i < n; i++)
{
Osoba.WczytajOsobe(out tmp, br);
List.AddToTail(mojaLista, tmp);
}
}
5. Otwórz plik Program.cs z projektu TestListyi dodaj do niego następujący
kod
a. Zaimportuj przestrzeń nazw System.IO
using System.IO;
b. Do metody Menu klasy Program dodaj dwie nowe pozycje menu:
"Zapisz listę" oraz "Wczytaj listę".
...
Console.WriteLine("\n\t\tG - Zapisz listę");
Console.WriteLine("\n\t\tH - Wczytaj listę");
...
c. Do metody Main klasy Program dodaj kod obsługujący nowo
dodane pozycje menu
...
case 'g':
case 'G':
ZapiszListe(mojaLista);
break;
case 'h':
case 'H':
WczytajListe(out mojaLista);
break;
...
.
.
.
public static void ZapiszListe(List mojaLista)
{
FileStream fs;
BinaryWriter bw=null;
Console.Write("Podaj nazwę pliku: ");
string nazwa = Console.ReadLine();
try
{
fs = new FileStream(nazwa,
FileMode.Create);
bw = new BinaryWriter(fs);
List.ZapiszListe(mojaLista, bw);
}
finally
{
if (bw != null)
bw.Close();
}
}
public static void WczytajListe(
out List mojaLista)
{
FileStream fs;
BinaryReader br=null;
Console.Write("Podaj nazwę pliku skąd
¬czytać dane: ");
string nazwa = Console.ReadLine();
try
{
fs = new FileStream(nazwa, ileMode.Open);
br = new BinaryReader(fs);
List.WczytajListe(out mojaLista, br);
}
finally
{
if (br != null)
br.Close();
}
}
6. Skompiluj i uruchom program.