Wstęp do programowania/Pliki

Z Studia Informatyczne
< Wstęp do programowania
Wersja z dnia 02:53, 1 paź 2006 autorstwa WBartkowski (dyskusja | edycje) (pierwsza wersja niesformatowana)
(różn.) ← poprzednia wersja | przejdź do aktualnej wersji (różn.) | następna wersja → (różn.)
Przejdź do nawigacjiPrzejdź do wyszukiwania

Pliki

Wprowadzenie Jak wiadomo architektura powszechnie stosowanych komputerów charakteryzuje się tym, że pamięć operacyjna jest ulotna, a jej rozmiar jest niewystarczający do przechowywania dużej liczby danych. W związku z powyższym komputery wyposażane są w pamięci masowe (zewnętrzne) pozwalające na trwały zapis danych o dużych rozmiarach. W rozważaniach teoretycznych często przyjmuje się, że rozmiar pamięć masowej jest nieograniczony.

Konstrukcja nośników danych wykorzystywanych do realizacji pamięci masowych sprawia, że dane zapisywane są i odczytywane w postaci wielobajtowych bloków. Blok danych zgodny z fizyczną charakterystyką danego użadzenia zwany jest rekordem fizycznym. Typowy rozmiary rekordu fizycznego mieści się w zakresie od 512 bajtów do kilku kilobajtów.

Fizyczne właściwości urządzenia na jakim składowane są dane nie powinny rozpraszać uwagi programisty. Działanie programu nie może być zależne od rodzaju nośnika. W związku z tym na poziomie systemu operacyjnego wprowadzono pojęcie pliku. Plik jest logiczną jednostką przechowywania danych. Aby cokolwiek przechować w pamięci masowej trzeba utworzyć co najmniej jeden plik. Warto w tym miejscu wspomnieć, że nie ma jednej uniwersalnej metody realizacji pojęcia pliku w systemie operacyjnym. Pliki realizowane są w strukturach zwanych systemami plików, które różnie wyglądają w różnych systemach operacyjnych. Często też w obrębie danego systemu operacyjnego mamy do wyboru wiele systemów plików o różnych właściwościach.

W pewnym uproszczeniu każdy plik możemy wyobrazić sobie jako ciąg kolejnych elementów. Za ostatnim znajduje się znacznik końca pliku. Element który zostanie przewożony przy kolejnym odczycie lub zapisie wskazywany jest przez wskaźnik pliku stojący na początku tego elementu. Proszę jednak pamiętać, że jest to bardzo uproszczony model mentalny pliku, bo choćby wskaźnik końca może być realizowane na różne sposoby. Czasami jest to wyróżniona wartość (inna niż dowolna przechowywana w pliku informacja) zapisana na końcu pliku, a w innych rozwiązaniach informacja o końcu pliku zapisana jest w strukturze systemu zbiorów zwanej FAT (file allocation table).

Ze względu na organizacje danych wewnątrz pliku (na poziomie logicznym) będziemy rozróżniać trzy typy plików: pliki binarne, pliki tekstowe i pliki ogólne.

Pliki binarne

Plik binarny to po prostu ciąg bajtów. Inaczej można powiedzieć, że jest to plik, w którym rekord logiczny ma rozmiar jednego bajta. Dane zawarte w pliku binarnym zawsze traktowane są jak ciąg bajtów, bez względu na rodzaj zapisanej w pliku informacji. Co więcej każdy plik możemy potraktować jak plik binarny i w pewnym sensie „ręcznie” (nie zdając się na system operacyjny) interpretować jego zawartość bajt po bajcie.

Pliki Ogólne

Plik ogólny podzielony jest na mniejsze równe porcje zwane rekordami logicznymi. Generalnie plik ogólny możemy wyobrazić sobie jako zbiór rekordów logicznych ułożonych jeden za drugim podobnie jak komórki w tablicy. W jeżyku Pascal mamy nawet pewne podobieństwa w składni deklaracji typu tablicy i pliku ogólnego. W obydwu przypadkach nazwa typu składowego występują po słowie kluczowym of. (array[1..n] of integer; file of integer;)

Rozmiar rekordu logicznego nie musi być równy rozmiarowi rekordu fizycznego. W związku z tym do manipulowania danymi w pliku potrzebny jest wydzielony obszar pamięci operacyjnej zwany buforem, w którym rekordy fizyczne „przerabiane” są na rekordy logiczne. Bufor stosowany jest również w pozostałych typach plików, z uwagi na efektywność zapisu. Jest ona większa gdy zapis lub odczyt obywa się odpowiednio dużymi porcjami będącymi całkowitą wielokrotnością rekordu fizycznego.

Pliki tekstowe

Pliki tekstowe zasługują na szczególną uwagę gdyż sposób kodowania informacji w tych plikach jest najbardziej rozpowszechnionym standardem, a także dlatego, że w wielu językach programowania stanowią one osobny wyróżniony typ danych z dodatkowym zestawem poleceń. Najprościej mówiąc plik tekstowy to ciąg znaków z podziałem na wiersze. Można powiedzieć, że podstawową jednostką danych w pliku tekstowym jest jeden znak, co w plikach zakodowanych w ASCII przekłada się na 1 bajt. Ale nie zawsze 1 znak to 1 bajt. W standardzie unicode jeden znak ma dwa bajty, a dodatkowo w standardzie tym występują sekwencje kilku bajtów oznaczające jeden znak. Również koniec linii kodowany jest na różne sposoby. W systemie Mac OS X jest to znak powrotu karetki, w systemie UNIX znak przejścia do nowego wiersza, a w systemie MS Windows koniec wiersza oznaczony jest dwoma znakami przejściem do nowego wiersza i powrotem karetki.

Rodzaje dostępu do pliku

Budowa pamięci masowej może tez powodować ograniczenia w sposobie dostępu do poszczególnych części pliku. Ze względu na te ograniczenia pliki możemy podzielić na pilik sekwencyjne i pliki o dostępie bezpośrednim.

Pliki o dostępie sekwencyjnym

Pliki o dostępie sekwencyjnym charakteryzują się ograniczoną swobodą poruszania się po rekordach. Aby przeczytać do k-ty rekord trzeba przeczytać poprzedzające go k-1 rekordów. Dodatkowo możemy przeskoczyć na początek albo na koniec pliku, ale nigdy w dowolne miejsce wewnątrz pliku.

Pliki o dostępie bezpośrednim

Pliki o dostępie bezpośrednim charakteryzują się tym, że rekordy logiczne są ponumerowane i można odczytywać je w dowolnym porządku dzięki operacji pozwalającej na przeskoczenie do rekordu o podanym numerze. W tego rodzaju plikach wszystkie rekordy logiczne musza mieć taki sam rozmiar.


Przetwarzanie plików

Generalnie każde przetwarzanie pliku odbywa się według schematu

Otwarcie pliku Operacje na danych w pliku Zamkniecie pliku

W momencie otwarcia pliku system tworzy deskryptor pliku (jeżeli wcześniej plik nie został otworzony), w którym przechowywane są między innymi takie informacje jak: określenie urządzenia pamięci masowej, na którym znajduje się plik; nazwa pliku; położenie bufora do przesyłania danych z i do pliku; i inne. W programie odwołujemy się do pliku przez zmienną, w której umieszczony jest wskaźnik (uchwyt) do deskryptora pliku. W Pascalu operacja otwarcia pliku rozbita jest na dwie instrukcje. Pierwsza Assign( f, nazwa_pliku) wiąże zmienną f z plikiem o podanej nazwie. Tworzony jest deskryptor i umieszczany wskaźnik do niego w zmiennej f. Następnie drugą instrukcją plik otwierany jest od odczytu reset(f), zapisu rewrite(f) lud dołączania danych appena(f); Reset ustawia wskaźnik pliku na początku i przygotowuje plik do odczytu. Rewrite kasuje zawartości pliku (wskaźnik jest na początku) i przygotowuje plik do zapisu, appena nie kasuje zawartości pliku, ustawia wskaźnik na końcu pliku i przygotowuje plik do zapisu.

Operacje na danych w pliku (odczyt lub zapis) W Pascalu jest to odpowiednio instrukcja read(f, zmienna), która powoduje odczyt danych z bieżącego rekordu logicznego (w pliku ogólnym) do zmiennej i przesuniecie wskaźnika pliku na początek kolejnego rekordu logicznego. W przypadku pliku tekstowego zachowanie instrukcji read jest zależne od typu zmiennej. Dla zmiennej typu char odczytywany jest jeden znak, a wskaźnik pliku przesuwa się do następnego zanku. Jeżeli zmienna jest typu liczbowego wtedy następuje odczytanie ciagu znaków odpowiadającego składniowo jednej liczbie danego typu. Jeżeli zmienna jest typu string to odczytywany jest ciąg znaków, aż do kończ biedzącego wiersza, a wskazie pliku zatrzymuje się przed znakiem końca wiersza. Aby przetworzyć znak końca wiersza należy wykonać polecenie readln(f). Po jego wykonaniu wskaźnik pliku ustawia się na początku kolejnego wiersza. Aby zapisać informacje w pliku należy użyć polecenia wreite(f, zmienna). Polecenie to spowoduje zapisanie zawartości zmiennej do pliku i ustawi wskaźnik pliku na końcu zapisanego rekordu (w pliku ogólnym). W przypadku pliku tekstowego liczba zapisanych znaków zależna jest od typu zmiennej. I tak odpowiednie dla typu char zapisze się jeden znak a wskaźnik pliku ustawi się za zapisanym znakiem. Dla typu liczbowego zapisze się ciąg znaków opowiadający danej liczbie. Dla typu string zapisane zostaną wszystkie znaki znajdujące się w zmiennej, a wskaźnik pliku ustawi się za ostatnim z zapisanych znaków. Aby zapisać znacznik końca linii należy użyć poleceni wreiteln(f).

Zamknięcie pliku W Pascalu zamknięcie pliku realizowane jest poleceniem Close(f). Uwaga, należy pamiętać o zamknięciu pliku nie tylko ze względu na zwolnienie pamięci zajmowanej przez deskryptor i bufor danych. Zamkniecie pliku ważne jest też ze względu na opóźniony zapis. Opóźniony zapis ma na celu zwiększenie efektywności zapisu danych w pamięci masowej. Polega on na tym, że dane z bufora nie są zapisywane dopóki nie zgromadzi się na tyle duża porcja by zapis był efektywny (np. porcja równa rozmiarowi rekordu fizycznego). Wiąże się z tym niebezpieczeństwo utraty danych jeżeli plik nie zostanie poprawnie zamknięty. Wtedy mogą zostać utracone dane, które gromadziły się w buforze i nie zostały jeszcze zapisane. Zamkniecie piku gwarantuje zapisanie danych zalegających w buforze. Czasami twórcy systemu operacyjnego stosują zabezpieczenie, które polega na tym, że w momencie gdy program kończy swoje działanie automatycznie zapisywane są dane z buforów dla wszystkich niezamkniętych plików używanych prze dany program. Podobne rozwiązanie stosowano kiedyś w drukarkach igłowych co przejawiało się dosyć zaskakującym zachowaniem drukarki, która czasami w momencie wyłączania ku zaskoczeniu użytkownika dodrukowywała brakująca cześć wiersza.

Dostęp bezpośredni Warto jeszcze wspomnieć, że w plikach ogólnych i binarnych o dostępie bezpośrednim możliwe jest bezpośrednie przejście do dowolnego rekordu logicznego. Operacje tą realizuje polecenie seek(f, N). Powoduje przejście do rekordu o numerze N gdzie 0 oznacza początek pliku.

Przykład przetwarzania plików ogólnego lub binarnego

var

 f:file of integer;

begin

 Assign(f, nazwa_pliku);
 Reset(f);
 while not Eof(f) do
   begin
     Read(f, zmienna);
   end;
 Close(f);

end.

Przykład przetwarzanie plików tekstowych var

 f:text

begin

 Assign(f, nazwa_pliku);
 Reset(f);
 while not Eof(f) do
   begin
     while not Eoln(f) do
       begin
         Read(f, zmienna)
         Przetwórz(zmienna)
       end;
     Readln(f);
   end;
 Close(f);

end.

Tu warto zwrócić uwagę na fakt, który często przykujemy za oczywisty. Powyższy program jest poprawny tylko przy założeniu, że eof(f) -> eoln(f). Gdyby ta implikacja nie zachodziła powyższy program wchodziłby w nieskończona pętlę po dojściu do końca pliku.