Umíme to s Delphi: 156. díl – paměťové proudy

Dnešní článek se opět zabývá otázkou datových proudů (streams). Na rozdíl od minulého dílu se však dnes namísto diskových souborů zaměříme na operační paměť: ukážeme si, jak pracovat s paměťovými datovými proudy reprezentovanými třídou TMemoryStream.

Dnešní díl seriálu je pokračováním v sérii článků týkajících se datových proudů (streams). Budeme pokračovat tam, kde jsme před týdnem skončili. Nejprve jen stručně zopakujeme, o čem jsme se dosud bavili.

Víme už, že stream funguje jako jakési vodovodní potrubí: na jedné straně do něho lijeme vodu, na straně druhé jej kamsi připojíme. V důsledku toho voda jednoduše protéká z nádrže do jejího cílového umístění. Podobně funguje stream: na jedné straně jej kamsi připojíme (např. souborový stream do diskového souboru), na druhé straně do něho lijeme data. Po úvodním nakonfigurování streamu („připojení trubky“) data jednoduše protékají tak, jak chceme.

Pomocí streamů lze přistupovat k načítání a ukládání informací do celé řady umístění, například souborový stream (TFileStream) umožňuje pracovat se soubory, paměťový stream (TMemoryStream) umožňuje pracovat s operační pamětí, řetězcový stream (TStringStream) umožňuje zapisovat do řetězců apod. Existují i streamy pro zapisování do databázových BLOB polí, existují streamy pro zapisování do síťových socketů apod.

Výhodou streamů je, že práce se všemi druhy je do určité míry unifikovaná, jinak řečeno – metody použité pro ukládání informací na disk lze bez velkých modifikací použít i pro ukládání informací do operační paměti apod. Vždy je pouze nutné učinit rozhodnutí týkající se použitého streamu a zvolit odpovídající třídu pro práci s odpovídajícím umístěním (paměť, disk).

V minulém dílu jsme následně vytvořili první praktickou (nebo spíše velmi nepraktickou :-)) aplikaci, na jejíž tvorbě jsme si demonstrovali, jakým způsobem zapisovat do streamu textové řetězce (což je v zásadě jedna z obtížnějších variant). Dnes budeme pokračovat a předvedeme si několik dalších věcí.

Jinak řečeno, v dnešním článku budeme potřebovat (a upravovat) aplikaci, kterou jsme vytvořili před týdnem. Pokud jste s námi před týdnem nebyli, pročtěte si prosím nejprve článek z minulého úterka, vytvoře podle zveřejněného návodu ukázkovou aplikaci (to bude otázka několika klepnutí myší a zkopírování zdrojového kódu z článku); potom se můžete bez obav vrátit sem a provádět kroky doporučené v následujícím textu.

Co vylepšit z minula?

Pojďme se tedy ještě chvíli dívat na aplikaci vytvořenou před týdnem. Tato aplikace nedělala nic moc světoborného – byla pouze schopná uložit obsah komponenty Memo do diskového souboru (reprezentovaného souborovým streamem – TFileStream) a následně velmi kostrbatě načíst obsah diskového souboru (opět prostřednictvím souborového streamu) zpět do komponenty Memo.

Aplikace byla velmi kostrbatá, především pak čtecí část. To jsem v článku nijak nezastíral: mimochodem, část oné kostrbatosti vznikla tím, že jsem při tvorbě aplikace nepoužil nejefektivnější konstrukce. V této souvislosti děkuji všem, kteří přispěli do diskuse a seznámili širokou veřejnost s elegantnějšími možnostmi.

Například, stream (třída TStream – předek všech streamů) disponuje vlastností Size, kterou lze použít právě pro zjištění velikosti (délky) streamu. V případě souborového streamu lze tuto vlastnost s výhodou použít ke zjištění délky souboru. Zdrojový kód „čtecí“ metody, tedy obsluhu události OnClick komponenty Button2, lze tedy přepsat a úplně vypustit původní úvodní částo otvírající soubor a zjišťující jeho velikost. Výsledný zdrojový kód bude vypadat takto:

procedure TForm1.Button2Click(Sender: TObject);
var x: TFileStream;             // stream

    delka: integer;             // delka souboru
    precteno: integer;          // pocet prectenych znaku

    buf: PChar;                 // pomocny buffer
    ret: string;                // precteny retezec

begin
  GetMem(buf, 1);               // vyhradime si 1bytovy buffer

  Precteno := 0;
  x := TFileStream.Create(`info.txt`, fmOpenRead);
                                // otevreme si stream pro cteni
  Delka:= x.Size;
  while (precteno < delka) do begin
                                // cteme az do konce souboru
    if (x.Read(buf^, 1) = 0) then break;
                                // pekne znak po znaku
    ret := ret + buf[0];        // a vytvarime vysledny retezec
    Inc(Precteno);
  end;

  if (precteno < delka) then
      ShowMessage(`Nektere udaje nebyly nacteny`)
    else
      ShowMessage(`Udaje byly uspesne nactny`);

  Memo1.Lines.Text := ret;      // retezec zobrazime v Memo
  FreeMem(buf);                 // uvolnime buffer
  x.Free;                       // uvolnime stream

end;

Podobných vylepšení bychom mohli realizovat ještě několik, ale to teď není tak podstatné. Pojďme dál.

Zásadní otázka týkající se celé uvedené aplikace může znít: proč si dáváme tolik práce s napsáním hloupé aplikace, jejíž jediná činnost spočívá v uložení textu na disk a v načtení textu z disku? Podobnou činnost bychom dokázala implementovat klidně za použití jediné řádky zdrojového kódu (vzpomínáte na metody Memo1.Lines.LoadFromFile, resp. Memo1.Lines.SavToFile?) a nemuseli bychom si vůbec lámat hlavu s datovými proudy, s alokací paměti pro řetězce ani s ničím jiným.

Odpověď je prostá: rozhodneme-li se pracovat se streamy, dostaneme k dispozici pozoruhodnou robustnost, jejíž praktické využití může spočívat třeba ve snadném přechodu na jiné paměťové médium.

Paměťový stream

Představme si třeba, že máme aplikaci a používáme v ní souborové streamy. Prostě jejich prostřednictvím ukládáme data do souborů a načítáme data ze souborů: aplikace je poměrně rozsáhlá a ukládání a načítání probíhá poměrně často.

Po nějakém čase můžeme například zjistit, že časté ukládání do souboru není nakonec zase tak nutné, protože většina ukládaných dat nemá kritickou důležitost a stejně je zase za chvíli načítáme zase zpátky za účelem dalšího zpracovávání. Nebo můžeme spatřit jiné důvody, to je teď celkem jedno: důležité je, že se rozhodneme přepracovat aplikaci tak, aby nepracovala pořád s diskovými soubory (což vyhodnotíme jako zbytečné, nákladné a pomalé) a rozhodneme se uchovávat některé informace po dobu běhu programu (nebo nějaké části jeho výpočtu) pouze v operační paměti.

Pokud bychom nepoužívali streamy, museli bychom složitě navrhovat a implementovat nějaký jiný model, který by takovou věc umožnil. V případě, že máme původní verzi aplikace navrženu za použití streamů, je řešení pozoruhodně prosté.

Pojďme si to demonstrovat na naší krásné ukázkové aplikaci vytvořené v minulém dílu seriál u (ačkoliv všichni vidíme, že v této konkrétní aplikaci je to v zásadě k ničemu). Předpokládejme, že obsah Mema nebudeme chtít nadále ukládat do souboru, ale budeme jej udržovat pouze v operační paměti.

Přechod ze souborového proudu na proud paměťový je pozoruhodně jednoduchý. Musíme jen mít na paměti několik maličkostí:

  • paměťové proudy jsou reprezentovány třídou TMemoryStream,
  • vytvoření paměťové proudu se provede jednoduše zavolání TMemoryStream.Create, přičemž konstruktor Create nemá v tomto případě žádné parametry,
  • následná práce s paměťovým streamem je zcela stejná jako práce se streamem souborovým (tedy pokud nechceme využívat specializované metody třídy TMemoryStream, což dnes nechceme), včetně volání metod Read a Write,
  • musíme si jen hlídat svou aktuální pozici v proudu, podrobně viz popis pod příkladem.

Pojďme si ukázat nový zdrojový kód:

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
    x: TMemoryStream;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);

begin
  x.SetSize(0);
  if (x.Write(PChar(Memo1.Lines.Text)^, Length(Memo1.Lines.Text)) < Length(Memo1.Lines.Text)) then
                                  // nacteme ulozime do nej udaje z Memo
    ShowMessage(`Nebyly zapsany vsechny znaky`)
  else
    ShowMessage(`Udaje byly uspesne zapsany`);

  Memo1.Lines.Clear;              // vymazeme obsah <emo

end;


procedure TForm1.Button2Click(Sender: TObject);
var
    delka: integer;             // delka souboru
    precteno: integer;          // pocet prectenych znaku

    buf: PChar;                 // pomocny buffer
    ret: string;                // precteny retezec

begin
  GetMem(buf, 1);               // vyhradime si 1bytovy buffer
  Delka := x.Size;
  x.position := 0;

  Precteno := 0;

  while (precteno < delka) do begin
                                // cteme az do konce souboru
    if (x.Read(buf^, 1) = 0) then break;
                                // pekne znak po znaku
    ret := ret + buf[0];        // a vytvarime vysledny retezec
    Inc(Precteno);
  end;

  if (precteno < delka) then
      ShowMessage(`Nektere udaje nebyly nacteny`)
    else
      ShowMessage(`Udaje byly uspesne nactny`);

  Memo1.Lines.Text := ret;      // retezec zobrazime v Memo
  FreeMem(buf);                 // uvolnime buffer

end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  x := TMemoryStream.Create;    // otevreme stream

  Button1.Caption := `Uloz`;
  Button2.Caption := `Nacti`;
  Self.Caption := `Ukazka streamu`;
end;


procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  x.Free;
end;

end.

Klíčové části zdrojového kódu zůstaly beze změny, zdrojový kód prošel jen několika málo úpravami.

Abychom nemuseli stream zbytečně a složitě předávat mezi jednotlivými metodami, definovali jsme stream x: TMemoryStream jako privátní atribut třídy formuláře:

  private
    { Private declarations }
    x: TMemoryStream;

Dále, upravili jsme obsluhu události OnCreate hlavního formuláře tak, aby k vytvoření streamu došlo ihned po startu aplikace:

procedure TForm1.FormCreate(Sender: TObject);
begin
  x := TMemoryStream.Create;    // otevreme stream

  Button1.Caption := `Uloz`;
  Button2.Caption := `Nacti`;
  Self.Caption := `Ukazka streamu`;
end;

Další změna spočívá v zařazení obsluhy události OnClose formuláře: v jejím těle pouze uvolníme proud z paměti.

Konečně, upravili jsme obsluhy události OnClick obou tlačítek. Změny však nejsou velké, týkají se v zásadě pouze reflektování změn zmíněných dosud, tedy vypuštění příkazu pro vytvoření proudu apod. Klíčové příkazy (zapsání do proudu, čtení z proudu apod.) zůstaly zcela beze změny.

Za zmínku stojí snad jen jedna věc, zato však velmi důležitá: u paměťového streamu musíme pečlivě střežit jeho vlastnost Position, která udává aktuální pozici v proudu. Jinak řečeno, pokud nenastavíme ve „čtecí“ obsluze vlastnost Position na 0, může se stát, že z proudu nic nenačteme, i když je naplněn a i když jsme jinak všechno udělali dobře.

Analogie platí v případě zapisování: pokud před zapsáním do proudu nenastavíme hodnotu jeho vlastnosti Position na 0, nebude stream přepsán novými údaji, ale zapisování bude probíhat na konec proudu (budeme vlastně do produ připisovat).

A ještě jedno doplnění: předchozí věta je pravdivá, až na to, že pokud bychom před začátkem zapisování pouze nastavili hodnotu Position na 0, budeme sice přepisovat od začátku údaje v proudu, ovšem pokud nové údaje budou starší než ty původní, pak zbytek těch původních v proudu zůstane. Jinak řečeno, zápis do proudu (byť od začátku) nezpůsobí vymazání předchozích údajů, které v proudu byly. Proud pak obsahuje zbytek starých dat.

Jedinou možností (a tu jsme také realizovali) je nastavit velikost proudu na 0 (použitím metody SetSize): tím vlastně vymažeme aktuální data v proudu a zároveň nastavíme aktuální pozici v proudu na jeho začátek. Do proudu pak můžeme cokoliv zapsat bez obavy, že nám v něm zůstane část původních údajů. Starosti o vlastnost Position a o čtení/zapisování ze/do aktuální pozice v proudu je v zásadě jediná významná změna.

Vidíme tedy, že realizovat změnu diskového proudu na paměťový není vlastně nijak obtížné. Jinak řečeno, změnit záznamové médium je v případě proudů velmi prosté. Nyní můžeme aplikaci pouze doplnit o jedno tlačítko, jehož stisknutí způsobí „vysypání“ paměťového proudu na disk: přidejte tedy na formulář ještě jednu komponentu Button a ošetřete její událost OnClick:

procedure TForm1.Button3Click(Sender: TObject);
begin
  x.SaveToFile(`info.txt`);
end;

Vidíme, že přelití obsahu paměťové proudu do diskového souboru je zcela triviální.

Zdrojový kód

Ani dnes vás neochudím o kompletní zdrojový kód příkladu:

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    Button2: TButton;
    Button3: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure Button3Click(Sender: TObject);
  private
    { Private declarations }
    x: TMemoryStream;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);

begin
  x.SetSize(0);
  if (x.Write(PChar(Memo1.Lines.Text)^, Length(Memo1.Lines.Text)) < Length(Memo1.Lines.Text)) then
                                  // nacteme ulozime do nej udaje z Memo
    ShowMessage(`Nebyly zapsany vsechny znaky`)
  else
    ShowMessage(`Udaje byly uspesne zapsany`);

  Memo1.Lines.Clear;              // vymazeme obsah <emo

end;


procedure TForm1.Button2Click(Sender: TObject);
var
    delka: integer;             // delka souboru
    precteno: integer;          // pocet prectenych znaku

    buf: PChar;                 // pomocny buffer
    ret: string;                // precteny retezec

begin
  GetMem(buf, 1);               // vyhradime si 1bytovy buffer
  Delka := x.Size;
  x.position := 0;

  Precteno := 0;

  while (precteno < delka) do begin
                                // cteme az do konce souboru
    if (x.Read(buf^, 1) = 0) then break;
                                // pekne znak po znaku
    ret := ret + buf[0];        // a vytvarime vysledny retezec
    Inc(Precteno);
  end;

  if (precteno < delka) then
      ShowMessage(`Nektere udaje nebyly nacteny`)
    else
      ShowMessage(`Udaje byly uspesne nactny`);

  Memo1.Lines.Text := ret;      // retezec zobrazime v Memo
  FreeMem(buf);                 // uvolnime buffer

end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  x := TMemoryStream.Create;    // otevreme stream

  Button1.Caption := `Uloz`;
  Button2.Caption := `Nacti`;
  Button3.Caption :=` Do souboru`;
  Self.Caption := `Ukazka streamu`;
end;

procedure TForm1.Button3Click(Sender: TObject);
begin
  x.SaveToFile(`info.txt`);
end;


procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  x.Free;
end;

end.

Závěrem

Dnešní článek ukázal, jak pracovat s dalším druhem streamů – s paměťovým streamem (reprezentovaným třídou TMemoryFile). Příště budeme v proudech pokračovat – ukážeme si, jak pracovat s dalším druhem proudů a řekneme si také více o metodách, které jsou pro práci s proudy k dispozici u tradičních komponent VCL.


Tip redakce: Změřte rychlost svého připojení k internetu s naší novou službou SpeedTest.cz

Diskuze (8) Další článek: Dnes volejte se Skype 10 minut zdarma

Témata článku: Software, Windows, Programování, Nejefektivnější způsob, Lily, Znak, Původní data, Proud, DEL, Nový údaj, Elsa, Nbsp, Stream, Zásadní otázka, Pravdivá informace, Private, Paměť, Díl, Lili


Určitě si přečtěte

USA rozdávají chudým dotované telefony s Androidem. Jsou z Číny a plné virů

USA rozdávají chudým dotované telefony s Androidem. Jsou z Číny a plné virů

** Chudí Američané mohou dosáhnout na dotovaný mobil ** Jeden takový rozdává třeba tamní Virgin Mobile ** Má to jeden háček. Je prošpikovaný malwarem

Jakub Čížek | 42

Pojďme programovat elektroniku: Rádiový čip, který má skoro každá bezdrátová myš

Pojďme programovat elektroniku: Rádiový čip, který má skoro každá bezdrátová myš

** Bezdrátové myši řídí čip od Nordic Semiconductors ** Jeho rádiové vysílače si před lety oblíbila i komunita kutilů ** Dnes si je vyzkoušíme v praxi

Jakub Čížek | 9

Co je TikTok: Svérázná sociální síť chytla mladé uživatele, už jich má už 1,5 miliardy

Co je TikTok: Svérázná sociální síť chytla mladé uživatele, už jich má už 1,5 miliardy

** Sociální síť TikTok získala stamiliony uživatelů a stále roste ** Jaký obsah na ní najdete a co můžete v jejím rámci čekat? ** Je to zábava pro mladé, nebo platforma pro úchyláky?

Karel Kilián | 37



Aktuální číslo časopisu Computer

Test 9 bezdrátových reproduktorů

Jak ovládnout Instagram

Test levných 27" herních monitorů

Jak se zbavit nepotřebných věcí na internetu