Wprowadzenie
W niniejszym rozdziale dowiesz się w jaki sposób można odczytywać wybrane fragmenty pliku bez konieczności wczytywania jego całej zawartości. Zanim jednak to nastąpi, warto najpierw poświęcić trochę czasu na teorię, a konkretniej na to, jak są przechowywane informacje w komputerze.
Logiczna budowa informacji przechowywanych w komputerze
Wszystko jest bajtem
W świecie informatyki i sprzętu komputerowego wszystko kręci się wokół jednostki
bajta. Za jej pomocą wyraża się rozmiar dysków twardych, rozmiar pamięci RAM, architekturę procesorów oraz wiele innych parametrów sprzętu komputerowego. Przyjęło się, że bajt jest najmniejszą logiczną paczką informacji na której pracuje komputer, zatem wszelkie niskopoziomowe narzędzia są przystosowane do pracy z jednym bajtem danych. Odczytując więc jakiekolwiek dane z dysku twardego/pamięci RAM czy też jakiegokolwiek innego nośnika informacji, zawsze będziesz musiał odczytać jeden bajt, nawet jeśli interesuje Ciebie tylko jeden bit do niego należący.
Dysk twardy, a jego logiczna struktura
Dysk twardy, płytę DVD czy pendrive-a możesz wyobrazić sobie jako jedną wielką jednowymiarową tablicę bajtów. W tej jednowymiarowej tablicy porozrzucane są dane plików, ich nazwy, katalogi i inne bajery, które są niezbędne do poprawnego odczytu informacji zapisanych na nośniku. Sporą część zajmują również bajty w których nie ma zapisanych żadnych użytecznych informacji i w każdej chwili możemy je wykorzystać na to, aby zapisać tam nowe informacje np. nowy plik na dysku. Praca na takiej jednowymiarowej tablicy z danymi byłaby jednak dla nas niewygodna, dlatego też system operacyjny zajmuje się zarządzaniem tej pamięci, dając nam w zamian ładną logiczną strukturę danych w postaci katalogów i plików. System operacyjny dostarcza więc nam cały zbiór narzędzi do wygodnej pracy z danymi zapisanymi na dysku.
Plik, a jego logiczna struktura
Jak już wiesz, dysk twardy możemy traktować w praktyce jako jednowymiarową tablicę. W niej znajdują się pliki, a dane jednego pliku mogą być (i zazwyczaj są) porozrzucane w różnych miejscach na dysku. Aby dało się jednak wygodnie pracować z plikiem, system operacyjny dostarcza nam narzędzia umożliwiające dostęp do pliku tak, jakby każdy plik był jedną i jednocześnie ciągłą tablicą jednowymiarową. Co więcej, tą jednowymiarową 'tablicę' pliku możemy sobie powiększać do woli, bądź zmniejszać w zależności od tego ile miejsca potrzebujemy na przechowanie interesujących nas informacji. System operacyjny oczywiście odmówi powiększenia pliku, jeżeli nie będzie miał już więcej wolnego miejsca na dysku, no ale nie o to w tym całym wywodzie chodzi :)
Pozycja w pliku
Jak już wcześniej się dowiedziałeś, każdy plik możemy traktować jak jednowymiarową tablicę z danymi. Dostęp do tej tablicy umożliwiają jak zwykle narzędzia dostarczone przez system operacyjny. Wspomniane narzędzia zostały stworzone tak, aby umożliwiały wygodną pracę z plikiem. Wymyślono więc, że każdy bajt pliku zostanie ponumerowany, a numeracja bajtów w każdym pliku będzie się zaczynała od zera. Pozycja w pliku określa więc numer bajta do którego chcemy się 'dostać' w celu odczytania bądź zapisania nowej informacji.
Podsumowanie teorii
Z kilku powyższych paragrafów powinieneś dowiedzieć się jak mniej więcej zorganizowane są dane na dysku twardym czy też płycie DVD. Pamiętaj, że przedstawiony powyżej opis jest bardzo ogólny oraz jest bardzo uproszczony w stosunku do rzeczywistości. Ważne jest to, abyś miał jakieś wyobrażenie jak są zorganizowane dane na dysku. Ważne jest również, abyś wiedział czym jest pozycja w pliku, bowiem reszta niniejszego rozdziału będzie koncentrowała się właśnie wokół tego terminu.
Określanie pozycji odczytu danych z pliku
Jak już wiesz do pracy z plikiem w trybie do odczytu wykorzystujemy klasę std::ifstream. W klasie tej znajdują się dwie metody o nazwie
seekg, które służą do ustawiania pozycji od której chcesz wczytywać dane z pliku. Pierwsza wersja metody
seekg przyjmuje jeden argument za pomocą którego określamy pozycję od której chcesz odczytywać dane z pliku. Pozycja ta musi być wyrażana względem początku pliku. W praktyce oznacza to, że wskazujesz po prostu numer znaku od którego chcesz rozpocząć czytanie pliku. Przykład:
std::ifstream plik;
plik.seekg( 10 );
plik.seekg( 1 );
plik.seekg( 0 );
Druga wersja metody
seekg posiada dwa argumenty. Pierwszym argumentem określa się 'o ile chcesz przesunąć pozycję odczytu', natomiast drugi argument określa 'względem jakiej pozycji chcesz przesunąć pozycję odczytu'. Argument pierwszy jest więc liczbą całkowitą (może być to wartość ujemna) , natomiast drugi argument może przyjąć jedną z trzech wartości:
Przesunięcie pozycji do odczytu na koniec pliku będzie więc wymagało napisania:
plik.seekg( 0, std::ios::end );
Przesunięcie pozycji odczytu danych o 3 znaki w lewo względem aktualnej pozycji w pliku będzie wymagało z kolei zastosowania zapisu:
plik.seekg( - 3, std::ios::cur );
Określenie pozycji odczytu względem początku pliku można zrobić na dwa sposoby:
plik.seekg( 5, ios::beg );
plik.seekg( 5 );
Pobieranie aktualnej pozycji odczytu danych z pliku
Utworzony obiekt typu std::ifstream, który wykorzystujemy do pracy z plikiem umożliwia również odczytywanie aktualnej pozycji odczytu danych z pliku. Do tego celu służy metoda
tellg, która nie przyjmuje żadnego argumentu, ale za to zwraca wartość, która określa aktualną pozycję odczytu danych z pliku. Zwracana pozycja jest wyrażona zawsze względem początku pliku. Przykład:
std::streampos iOdczytanaPozycja = plik.tellg();
std::cout << "Aktualna pozycja odczytu danych z pliku: " << iOdczytanaPozycja << std::endl;
Warto również w tym miejscu wspomnieć, że metodę
tellg można wykorzystać przy określaniu pozycji odczytu danych z pliku:
plik.seekg( 8, ios::cur );
plik.seekg( plik.tellg() + 8 );
Przykład
#include <iostream>
#include <fstream>
int main()
{
std::ifstream plik( "dane.txt" );
if( !plik.good() )
return 0;
std::streampos iPozycjaStartowa = 4;
plik.seekg( iPozycjaStartowa );
int iLiczba;
plik >> iLiczba;
int iWczytanychZnakow = plik.tellg() - iPozycjaStartowa;
std::cout << "Liczba: " << iLiczba << std::endl;
std::cout << "Wczytano " << iWczytanychZnakow << " znakow" << std::endl;
return 0;
}
Podsumowanie
Po przeczytaniu niniejszego rozdziału powinieneś już wiedzieć w jaki sposób poruszać się po pliku w trybie do odczytu. Wiedz jednak, że dostęp do danych przechowywanych na dysku jest kosztowny czasowo i dla małych plików z danymi znacznie lepiej i wygodniej wczytać całe dane do pamięci, unikając tym samym zbędnego poruszania się po pliku. Korzyści z poruszania się po pliku zaczynają być zauważalne dopiero przy wyszukiwaniu danych w dużych i dobrze zorganizowanych zbiorach danych, takich jak np. wszelkiego rodzaju bazy danych ze ściśle określoną strukturą danych. Generalnie rzecz biorąc, pracując z plikami należy dążyć do minimalizacji liczby odczytów dyskowych, starając się jednocześnie odczytywać duże bloki pamięci jednym wywołaniem metody odczytującej dane. Traktuj tą informację na chwilę obecną jako ciekawostkę, a aktualnie staraj się pisać przede wszystkim prosty i krótki kod.
Zadanie domowe