Umíme to s Delphi: 155. díl – streamy prakticky

Dnešní článek se bude zabývat praktickou stránku datových proudů neboli streamů. Ukážeme si, jakým způsobem streamy prakticky použít k ukládání informací na disk a k jejich opětovnému načítání z disku. Poukážeme také na některá úskalí, které je nutno mít na paměti při práci s textovými řetězci.

Dnešní díl seriálu naváže na to, co jsme začali probírat před týdnem. V minulém článku jsme otevřeli problematiku tzv. datových proudů neboli streamů. Řekli jsme si úplné teoretické základy týkající se toho, co to streamy vlastně jsou, k čemu slouží, jaké druhy streamů existují a jaké základní operace se streamy máme k dispozici.

Víme už tedy, ž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).

Třídy pro práci se streamy poskytují sadu metod umožňujících zapisovat a načítat data. Dostupných „streamových“ tříd existuje celá řada, mají však jedno společné: všechny jsou odděleny od společného předka – od třídy TStream. Z tohoto faktu také plyne to, co jsem si uvedli před okamžikem - všechny streamy mají do určité míry podobné chování a ovládání.

V minulém článku jsme si ještě ukázali základní obecné metody pro práci takřka se všemi druhy streamů. Pojďme si jen v rychlosti zopakovat jejich funkční prototypy:

function Read(var Buffer; Count: Longint): Longint;
function Write(const Buffer; Count: Longint): Longint;
procedure ReadBuffer(var Buffer; Count: Longint);
procedure WriteBuffer(const Buffer; Count: Longint);

Tolik k zopakování toho, co jsme si řekli minule. Náplň dnešního dílu bude ryze praktická: ukážeme si, jak prostřednictvím streamu realizovat několik jednoduchých, avšak ryze praktických operací.

Ukázková aplikace: souborový stream

Pojďme společně vytvořit aplikaci, která demonstruje základy práce se streamy. Pro ukázku využijeme souborový stream, budeme tedy pracovat s diskovým souborem.

Nejprve se podívejme na to, jak stream použít k jednoduchému zapisování na disk, resp. ke čtení informací z disku.

Dnešní aplikace se zaměří na specifickou formu práce se streamy, a to na ukládání/načítání řetězců. Skutečností je, že pro ukládání řetězců se streamy zase až tak často nepoužívají: k tomu existují specializované metody jednak proto, že řetězce jsou jedním z nejčastěji používaných datových formátů a pak také proto, že řetězce mají všemožná specifika, na něž je nutné při jejich zpracování vzít zřetel.

Přesto však řetězci začneme, především proto, že jsem si to tak naplánoval :-) a pak také proto, že na následující aplikaci uvidíme hned několik úskalí práce se streamy.

Vytvořme tedy novou aplikaci. Na formulář umístíme dvě tlačítka (komponenty Button) a jednu komponentu Memo. Funkce aplikace bude následující:

  • po klepnutí na první tlačítko dojde k uložení obsahu komponenty Memo do souboru info.txt do aktuálního adresáře na disk počítače, zároveň dojde k vymazání obsahu Mema,
  • po klepnutí na druhé tlačítko se načte obsah souboru info.txt z aktuálního adresáře na disku počítače; tento načtený obsah se následně zobrazí v komponentě Memo.

Je mi jasné, a proto mi na toto téma nemusíte posílat rozhořčené emaily :-), že podobného výsledku by bylo možné dosáhnout s nesrovnatelně menším úsilím použitím nativních metod komponenty Memo, například LoadFromFile nebo SaveToFile. Použití streamů je zde zvoleno z čistě demonstračních důvodů, abyste viděli, jak do streamu „nacpat“ řetězec a abyste viděli, jak řetězec ze streamu zase „vydolovat“. Neříkám, že zvolené řešení je optimální, neříkám ani, že ne nejlepší možné, neříkám dokonce ani, že je příliš elegantní.

Pojďme se však vrátit k tvorbě aplikace. Na formulář jsme umístili potřebné komponenty, nyní pojďme ošetřit několik málo událostí.

Začneme událostí OnCreate hlavního formuláře. V její obsluze pouze nastavíme titulky komponent:

procedure TForm1.Button1Click(Sender: TObject);
var x: TFileStream;

begin
  x := TFileStream.Create(`info.txt`, fmCreate or fmOpenWrite);
                                  // otevreme stream
  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
  x.Free;                         // uvolnime stream

end;

Uvedený zdrojový kód je poměrně samovysvětlující, proto popisek bude jen velmi kusý:

nejprve vytvoříme stream (procedura TFileStream.Create),

následně do vytvořeného streamu zapíšeme pomocí funkce Write obsah vlastnosti Memo1.Lines.Text, což je řetězcová reprezentace obsahu komponenty Memo, vypíšeme hlášení o výsledku operace, vymažeme obsah Memo a uvolníme stream.

Pokud tento kód napíšete nebo zkopírujete do Delphi, můžete si aplikaci rovnou vyzkoušet: na diskovém souboru v aktuálním adresáři po kliknutí na tlačítko Button1 nepochybně najdete soubor info.txt, který obsahuje doslovný přepis původního obsahu komponenty Memo.

Nyní se pojďme podívat na obsluhu tlačítka Button2. Stisk tohoto tlačítka nezpůsobí nic jiného, než načtení souboru info.txt z disku a jeho zobrazení v komponentě Memo. Zdrojový kód je mírně komplikovanější: na vině jsou specifika řetězců a také určitá neelegance, s níž streamy v tomto kontextu používáme:

procedure TForm1.Button2Click(Sender: TObject);
var x: TFileStream;             // stream
    f: File of byte;            // soubor

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

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

begin
  AssignFile(f, `info.txt`);    // otevreme soubor
  Reset(f);                     // jen abychom mohli
  Delka := FileSize(f);         // zjistit jeho delku = pocet znaku
  CloseFile(f);                 // soubor zase pekne zavreme
  GetMem(buf, 1);               // vyhradime si 1bytovy buffer


  Precteno := 0;
  x := TFileStream.Create(`info.txt`, fmOpenRead);
                                // otevreme si stream pro cteni
  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 nacteny`);

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

end;

Vidíte, že zdrojový kód je trochu kostrbatý a vyžaduje některé „úkroky stranou“, které ve zdrojácích neradi vidíme. Po pravdě řečeno, mám-li být upřímný, nemyslím si, že by tento kód vyjadřoval nejlepší možné řešení: pakliže některý z pozorných čtenářů zná elegantnější řešení, budu rád, když mi jej zašle na email nebo případně rovnou zveřejní ve formě diskusního příspěvku.

Nevím, má-li smysl detailně popisovat funkci uvedené procedury. Možná jen velmi stručně:

  1. Nejprve otevřeme soubor info.txt (AssignFile, Reset), abychom zjistili jeho délku (FileSize). Zjištění délky souboru je jediný způsob, jak se dopředu dozvědět, kolik bychom toho měli načíst.
  2. Důležité je, že při načítání pomocí streamu musíme použít dynamickou paměť reprezentovanou ukazatelem. Definujeme tedy proměnnou typu PChar (ukazatel na znak), které následně vyhradíme fyzické místo v paměti (GetMem). Soubor budeme načítat znak po znaku, budeme tedy potřebovat jen jednoznakovou dynamickou paměť.
  3. Následně otevřeme stream (TFileStream.Create).
  4. Potom pracujeme ve smyčce: čteme ze streamu znak po znaku až do okamžiku, kdy přečteme všechny znaky ze souboru (precteno = delka) nebo kdy z nějakého důvodu čtení selže (funkce Read vrátí nulu).
  5. Přečtený znak vždy přidáme do výsledného řetězce, který vytváříme v proměnné ret.
  6. Pak jen vypíšeme hlášení o výsledku operace.
  7. Nakonec zobrazíme řetězec ret v komponentě Memo, uvolníme jednoznakovou dynamickou paměť a uvolníme stream.

Klepněte pro větší obrázek

Ať už je řešení hezké nebo ne, skutečnost je taková, že funguje a splňuje stanovené zadání: aplikace je schopna ukládat a znovu načítat obsah komponenty Memo z diskového souboru.

Protože jsme se v tomto okamžiku už prudce přiblížili ke konci článku, přerušíme pro tento okamžik tok jeho textu a na týden se rozloučíme.

Za týden bezprostředně navážeme a ukážeme si to, k čemu vlastně celý dnešní článek směřoval:

  • jednoduchá změna, která způsobí, že namísto diskového souboru budeme používat jen operační paměť (tzv. paměťový stream),
  • výhody, které nám může přinést použití řetězcových stringů (TStringStream),
  • jaké předdefinované metody pro práci se streamy jsou k dispozici u řady vizuálních komponent, například u ListBoxu apod.

Zdrojový kód

Závěrem si znovu předvedeme kompletní zdrojový kód dnešní aplikace.

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 }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var x: TFileStream;

begin
  x := TFileStream.Create(`info.txt`, fmCreate or fmOpenWrite);
                                  // otevreme stream
  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
  x.Free;                         // uvolnime stream

end;


procedure TForm1.Button2Click(Sender: TObject);
var x: TFileStream;             // stream
    f: File of byte;            // soubor

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

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

begin
  AssignFile(f, `info.txt`);    // otevreme soubor
  Reset(f);                     // jen abychom mohli
  Delka := FileSize(f);         // zjistit jeho delku = pocet znaku
  CloseFile(f);                 // soubor zase pekne zavreme
  GetMem(buf, 1);               // vyhradime si 1bytovy buffer


  Precteno := 0;
  x := TFileStream.Create(`info.txt`, fmOpenRead);
                                // otevreme si stream pro cteni
  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;

procedure TForm1.FormCreate(Sender: TObject);
begin
  Button1.Caption := `Uloz`;
  Button2.Caption := `Nacti`;
  Self.Caption := `Ukazka streamu`;
end;

end.

Na závěr

Dnes jsme si ukázali, jakým způsobem lze použít souborové streamy pro ukládání souborů na disk a pro načítání souborů z disku. Dnešní článek sice končí, ale dokončený není: příště na něj bezprostředně navážeme a ukážeme si skutečné výhody, které streamy přinášejí.

Diskuze (7) Další článek: Nejvyšší soud USA: Grokster prohrál svůj boj

Témata článku: Software, Windows, Programování, Fyzické místo, Lili, Ukázková aplikace, Stream, Str, Díl, DEL, Reset, Znak, Dokončené dílo, Lily


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

Nvidia představila grafické karty GeForce RTX 3090, RTX 3080 a RTX 3070. Známe české ceny

Nvidia představila grafické karty GeForce RTX 3090, RTX 3080 a RTX 3070. Známe české ceny

** Nvidia uvedla nové desktopové grafické karty GeForce RTX 3000 ** Jedná se o modely GeForce RTX 3070, 3080 a 3090 ** K výrobě se používá 8nm technologii od Samsungu

Karel Javůrek | 67

Je lepší hrát na PC, či na konzolích? Nebo jsou i jiné možnosti?

Je lepší hrát na PC, či na konzolích? Nebo jsou i jiné možnosti?

** Jaké jsou výhody a nevýhody hraní na počítači? ** Co mají společného a v čem se liší Xbox One, PS4 a Switch? ** Na čem hrát, když nemáte výkonné PC ani konzoli?

Lukáš Václavík | 125

10 věcí, které nás štvou na Windows 10 a bohužel asi jen tak nepřestanou

10 věcí, které nás štvou na Windows 10 a bohužel asi jen tak nepřestanou

** Windows 10 je na trhu 5 let, ale pořád má velké rezervy ** Ani desátá velká aktualizace, která vyjde na podzim, je nevyřeší ** Štvou nás Windows Update, Store, Nastavení atd.

Lukáš Václavík | 146

Zapomeňte na kometu, české nebe každý den křižují mnohem zajímavější kousky

Zapomeňte na kometu, české nebe každý den křižují mnohem zajímavější kousky

** České nebe každý den křižuje hromada exotických letounů ** Na populární mapě Flightradar24 je ale nenajdete ** Jsou to vojenské letouny USA, UK a NATO

Jakub Čížek | 39

Zahodil jsem Windows, přešel na Linux a nezbláznil se z toho

Zahodil jsem Windows, přešel na Linux a nezbláznil se z toho

** Měsíc jsem se nedotkl Windows a byl závislý jen na Linuxu ** Jaká byla pozitiva a negativa přechodu? ** Se kterými aplikacemi jsem (ne)zápasil a které bych doporučil?

Lukáš Václavík | 242


Aktuální číslo časopisu Computer

Megatest mobilů do 8 000 Kč

Test bezdrátových headsetů

Linux i pro začátečníky

Jak surfovat anonymně