artykuły

Delphi - Strumienie - TMemoryStream

20:36
pon, 12 lipiec 2004
Artykuł opisuje na przykładach działanie i zastosowania strumieni typu TMemoryStream. W prosty sposób wyjaśnia jak uniknąć częstych błędów związanych ze strumieniami.

#3 TMemoryStream

Wstęp

Witam po raz kolejny. Dzisiaj zajmiemy się strumieniem typuTMemoryStream. Ten typ strumienia umożliwia nam przechowywanie rozmaitych informacji. Możemy go zatem wykorzystywać jako pamięć podręczną programu. I tak się właśnie robi.TMemoryStreamjest przeważnie wykorzystywany do przechowywania tymczasowych informacji, które po zamknięciu programu nie będą już potrzebne. Oczywiście, tak jak w pozostałych strumieniach, mamy możliwość zapisania jego treści do pliku.
TMemoryStreampozwala nam przykładowo na skrócenie czasu potrzebnego do przeprowadzenia operacji. Rozważmy taką sytuację: wkładamy dyskietkę z małym plikiem tekstowym. Odczytujemy kolejno od końca (do początku) litery zamieniając je na WIELKIE, a następnie zapisujemy przekładając je na początek pliku. Co prawda operacja nie ma większego sensu, ale ma rozwiązanie. Możemy tego dokonać nie tylko za pomocą zmiennej typuString, ale również poprzez strumienie - dajmy na toTMemoryStream. Ładujemy tekst z dyskietki do strumienia. Wykonujemy operacje, a następnie zapisujemy tekst z powrotem na nośniku. Skróci to czas wykonywania operacji, a my zyskamy przy tym wygodne narzędzie do pracy z danymi.

Wszystkie prezentowane przeze mnie typy strumieni są do siebie podobne. Oczywiście to samo dałoby się zrobić naTFileStream- nie wykluczam. Dlatego zajmiemy się innymi przypadkami.

Trochę wiedzy...

... nie zaszkodzi, a może skutecznie skrócić czas potrzebny do napisania aplikacji.
Pisząc programy, często zastanawiamy się jak coś zrobić, aby się nie narobić ;) Mam dla was przykład:

initialization strumien := TMemoryStream.Create; end.

Powiecie - "co??? Jak można pisać jakiś kod poza kodem danej procedury?". Okazuje się, że można. Powiem więcej - czasami nawet trzeba! Ten kawałek kodu umieszczamy poza wszystkimi procedurami, ale zaraz przed słówkiemend.(endz kropką na końcu). Domyślacie się już co nam umożliwia? Hmm...initialization- brzmi znajomo - podobny jest spolszczony odpowiednik - inicjacja, czyli w tym wypadku "przygotowanie czegoś do działania". Tak, jeśli tutaj wpiszemy kod odpowiedzialny za stworzenie nam strumienia, który przedtem globalnie zadeklarowaliśmy (przed sekcjąimplementation) to możemy się pokusić o używanie go w każdej procedurze bez konieczności jego tworzenia, czyli po prostu wpisać w dowolnej procedurze np.strumien.LoadFromFile()i już. Dane są przechowywane w pamięci globalnej naszego programu, czyli nie będą kasowane gdy dana procedura się zakończy.
Kiedy jest wykonywany kod w sekcjiinitialization? Na samym początku działania programu. Zanim jeszcze nawet zostaną wykonane instrukcje zawarte w procedurze OnCreate (!). Możecie to sprawdzić pisząc winitialization:

initialization ShowMessage('Komunikat 1 z sekcji initialization');

a w procedurze OnCreate pisząc:

ShowMessage('Komunikat 2 z procedury OnCreate');

Zauważcie, że najpierw zobaczycie "Komunikat 1", a dopiero potem "Komunikat 2" (łatwo to również zauważyć na debugerze).

Metody (czyli procedury i funkcje) TMemoryStream

    Krótkie przypomnienie nie zawadzi. Podstawowe funkcje zapisujące, odczytujące prezentują się następująco:

Funkcja Read

function Read(var Buffer; Count: Longint): Longint;

Pierwszym parametrem jest bufor do którego odczytywane są dane, drugi parametr to, jak wiemy, ilość danych która ma zostać odczytana. Funkcja zwraca (w bajtach) ilość danych które udało jej się odczytać.

Funcja Write

function Write(const Buffer; Count: Longint): Longint;

Pierwszym parametrem tej funkcji zapisującej są dane które ma zapisać, drugi parametr "mówi" funkcji ile danych ma z nich zapisać. Funkcja zwraca ilość danych (w bajtach) które udało się jej zapisać.

Procedura LoadFromFile()

procedure LoadFromFile(const FileName: string);

Tak, jak w innych typach strumieni, i w tym mamy do czynienia z procedurą, która pozwala nam na wczytanie pliku bezpośrednio do naszego strumienia. Pierwszym i jedynym parametrem tej procedury jest ścieżka do pliku który ma wczytać do strumienia.

Procedura LoadFromStream()

procedure LoadFromStream(Stream: TStream);

Procedura ta umożliwia nam wczytanie danych z innego strumienia. Jako pierwszy parametr powinniśmy podać nazwę strumienia z którego zamierzamy wczytać dane.

Procedura SaveToFile()

procedure SaveToFile(const FileName: string);

Jest to procedura, która pozwala nam na zapisanie do pliku całej zawartości naszego strumienia. Pierwszym i jedynym parametrem tej procedury jest ścieżka do pliku który procedura ma zapisać pliku.

Procedury SaveToStream()

procedure SaveToStream(Stream: TStream);

Procedura ta umożliwia nam zapisanie danych do innego strumienia. Jako pierwszy parametr powinniśmy podać nazwę strumienia, do którego zamierzamy zapisać dane.

Przypomnienie z części #2:

"WriteBuffer i ReadBuffer

W zasadzie procedury te nie różnią się zbytnio od funkcjiWriteiRead, poza jednym wyjątkiem - nie zwracają one żadnych danych. Tak więc proceduraWriteBuffernie zwróci nam ilości zapisanych danych do pliku, tak jak to miało miejsce przy funkcjiWrite, a proceduraReadBuffernie zwróci nam ilości danych odczytanych z pliku (jak to miało miejsce przyRead)."

WriteComponent

procedure WriteComponent(Instance: TComponent);

Oczywiście i w tym typie strumienia istnieje możliwość zapisu całego komponentu bezpośrednio do naszego strumienia dokładnie tak jak to robiliśmy omawiając strumieńTFileStream, z tą różnicą, że tutaj dane nie są w żaden sposób zapisywane do pliku, choć istnieje taka możliwość. Pierwszym i jedynym parametrem jest nazwa komponentu, który chcemy zapisać.

ReadComponent

function ReadComponent(Instance: TComponent): TComponent;

Funkcja ta jest odwrotnościąWriteComponenti służy do wczytywania uprzednio zapisanego komponentu. Pierwszym i jedynym parametrem jest komponent, który ma zostać odczytany. Funkcja zwraca komponent ;) Korzystamy z niej tak, jak to zostało zademonstrowane w przykładzie części #2.

Zaczynamy... ;)

Aby użyć strumienia TMemoryStream należy na początku procedury najpierw zadeklarować zmienną jako typ strumienia, popatrzcie:
var strumien : TMemoryStream txt : string; // Będziemy potrzebować tej zmiennej - przechowuje ona tekst Dalej, po słowie begin musimy utworzyć strumień. Aby utworzyć strumień, piszemy:
strumien := TMemoryStream.Create;Przy czym zauważcie, że w tym wypadku konstruktor Create nie potrzebuje żadnych dodatkowych parametrów.
Przyszedł teraz czas na zapisanie danych do strumienia, a następnie ich późniejsze odczytanie. Piszemy więc :
txt := 'Ble, ble, ble - zawartość strumienia'; strumien.Write(txt,SizeOf(txt)); strumien.Position := 0; txt := ''; strumien.Read(txt,SizeOf(txt)); ShowMessage(txt); strumien.Free; Pierwsza linijka jest przypisaniem tekstu przeznaczonego do zapisania do zmiennejtxt.
Druga linijka zapisuje zmienną txt do strumienia, przy czym drugi parametr to rozmiar zapisywanych danych.
Trzecia linijka ustawia kursor na początku strumienia ( funkcjaWriteprzesuwa kursor o tyle ile znaków zapisała, więc aby odczytać cokolwiek, należy przesunąć kursor do początku strumienia ).
Czwarta linijka zeruje nam zmiennątxt( aby było pewne, że funkcjaReadodczytała cokolwiek, a nie zostawiła zmiennej taką jaką była )
Piąta linijka jest wczytaniem do pustej zmiennejtxttekstu ze strumienia.
Szósta linijka wyświetla nam komunikat z odczytanym tekstem.
Siódma linijka to zwolnienie pamięci strumienia.

Tak naprawdę do zapisu potrzebujemy jedynie dwóch linii kodu. Ok, myślę, że przyszła pora na coś trudniejszego. Pamiętacie jak na początku wspomniałem o sekcjiinitialization? Teraz ją wykorzystamy. Do dzieła!
Zbudujemy program w którym nie będziesz potrzebował tworzyć strumienia, a następnie go zwalniać w każdej procedurze. Otóż, jak wspomniałem, istnieje sekcjainitialization, która, jak się przekonaliśmy, jest wykonywana zaraz przy starcie programu. W niej możemy utworzyć strumień. Ale musi być do niej (logicznie) jakieś przeciwieństwo. I tym właśnie jest sekcjafinalization. Instrukcje zawarte w sekcjifinalizationsą wykonywane po zakończeniu programu. Spróbujmy napisać przykładowy program.

Piszemy program

  1. Umieść na formie dwa przyciski (Button) (paleta Standard).
  2. Przyda się także komponentMemo(również paleta Standard).
  3. Wszystko ładnie rozmieszczamy.
  4. Przypuśćmy, że przycisk o nazwie Button1, będzie naszym przyciskiem odczytującym zawartość strumienia, a Button2, przyciskiem zapisującym informacje do strumienia. Zmieńmy więc właściwośćCaptionkomponentu Button1 na "Odczytaj", a komponentu Button2 na "Zapisz".
  5. Klikamy dwukrotnie na przycisk "Odczytaj" wywołując tym samym edytor kodu z wygenerowaną procedurą OnClick.
  6. W treści tej procedury wpisujemy:

    strumien.Position := 0; // Po zapisie kursor jest na końcu pliku, więc trzeba go cofnąć z powrotem na początek. memo1.Lines.LoadFromStream(strumien); // Załadowanie do komponentu Memo1 zawartości strumienia
  7. Klikamy dwukrotnie na przycisk "Zapisz" i w edytorze kodu wprowadzamy do nowo-wygenerowanej procedury odpowiedni kod:

    strumien.Clear; // Wyczyszczenie strumienia memo1.Lines.SaveToStream(strumien); // Zapisanie zawartości komponentu Memo1 do strumienia memo1.Clear; // Wyczyszczenie komponentu Memo1
  8. Do sekcjivarglobalnego (tego przed słowemimplementation) dodajemy deklarację:
    strumien : TMemoryStream;
  9. Przenosimy się na koniec kodu i zaraz przed słówkiem end. (end z kopką) (poza wszelkimi procedurami) wpisujemy następujący kod:
    initialization strumien := TMemoryStream.Create; // Utworzenie strumienia finalization strumien.Free; // Zwolnienie pamięci strumienia
  10. Przyduśmy teraz klawisz F9 uruchamiając tym samym nasz program.

Mam nadzieję, że program działa. Przeniesienie strumienia do zmiennych globalnych daje nam możliwość skorzystania ze strumienia w dowolnym miejscu naszego programu.
Jeszcze tylko pozostaje kwestia, dlaczego wyczyściłem strumień przed zapisem do niego. Wyczyściłem go, ponieważ podczas zapisu do strumienia, dane w nim są dodawane do pozostałych, tak więc jeśli w strumieniu jest już tekst np. "Pierwsze słowo" po zapisie następnego słowa, będziemy mieć w strumieniu tekst: "Pierwsze słowoDrugie słowo", dlatego aby uniknąć powtarzania się zapisanego tekstu, przed jego zapisem zawartość strumienia jest czyszczona.

Zakończenie

Wykorzystać strumienie można na wiele sposobów. Sami przyznacie, że jest to wygodny sposób zarówno na zapis jak i przetwarzanie danych. Czytajcie helpy Delphiego (w nich jest najwięcej informacji), pomogą one Wam zrozumieć niektóre funkcję i czy chcecie czy nie, podszkolą was w angielskim ;)

12345
Delphi - Strumienie - TMemoryStream Autor opinii: Czytelnicy, data przesłania: 5

Podobne artykuly:

Skomentuj

Aby zamieścić komentarz, proszę włączyć JavaScript - niestety roboty spamujące dają mi niezmiernie popalić.






Komentarze czytelników

    • Konrado4
    • sob, 7 luty 2009, 22:00
    • Podstawy Okej super. Ale ja borykam sie z innym problemem. Czy memory Streem moze działac tak jak plik z dysku? potrzebuje zrobic program kóry deszyfruje plik xml (z baza danych) do pamieci (bez zapisu rozszyfrowanych danych na HDD) i zebym mogł wczytac streem jako plik do ClentDataSet.loadFromFile

      jak to zrobić ?




      Odp: Spróbuj użyć procedury: ClientDataSet.LoadFromStream()
      podając jako argument obiekt strumienia o którym piszesz.
Dexter