Umíme to s Delphi, 25. díl – práce se soubory podrobněji, 2. část

Dnes budeme pokračovat v tom, co jsme „nakousli“ minule – v podrobném popisu práce se soubory. Nejprve si vysvětlíme vyhledávání pomocí procedur FindFirst a FindNext, a pak se konkrétně a podrobně podíváme na specifika práce s jednotlivými typy souborů. Vytvoříme také několik demonstračních aplikací.
Vyhledávání souborů – FindFirst, FindNext

Než se podrobně podíváme na jednotlivé typy souborů, uvedeme si příklad na použití procedur FindFirst a FindNext, které se s výhodou používají k vyhledávání souborů.

Procedury FindFirst a FindNext se zpravidla používají jako komplementární :-) dvojice. Následující příklad vyhledá v zadaném adresáři všechny soubory, jejichž velikost je větší nebo rovna než zadané číslo.

V aplikaci se vyskytují následující komponenty:

  • formulář (frmHlavni);
  • 2x nápis Label (lblAdresar a lblVelikost);
  • 1x editační pole Edit (edtAdresar);
  • 1x editační pole SpinEdit (sedtVelikost);
  • 2x tlačítko (btnVyhledej a btnKonec);
  • 1x seznam ListBox (lbSoubory).
Všechna důležitá úvodní nastavení komponent jsou z didaktických důvodů provedena v rámci ošetření události OnCreate hlavního formuláře. Hlavní akce (vyhledávání) je provedena v ošetření události OnClick tlačítka btnVyhledej.

Zdrojový kód celého modulu:

unit Hlavni;

interface

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

type
  TfrmHlavni = class(TForm)
    edtAdresar: TEdit;
    sedtVelikost: TSpinEdit;
    lblAdresar: TLabel;
    lblVelikost: TLabel;
    btnVyhledej: TButton;
    btnKonec: TButton;
    lbSoubory: TListBox;
    procedure FormCreate(Sender: TObject);
    procedure btnVyhledejClick(Sender: TObject);
    procedure btnKonecClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  frmHlavni: TfrmHlavni;

implementation

{$R *.DFM}

procedure TfrmHlavni.FormCreate(Sender: TObject);
begin
  frmHlavni.Caption := `Vyhledání souborů`;

  lblAdresar.Caption := `Zadejte adresář:`;
  lblVelikost.Caption := `Zadejte velikost v kB:`;

  btnVyhledej.Caption := `&Vyhledej`;

  btnKonec.Caption := `&Konec`;

  edtAdresar.Text := ``;

  sedtVelikost.MinValue := 0;
  sedtVelikost.MaxValue := 100000;
  sedtVelikost.Value := 100;
end;

procedure TfrmHlavni.btnVyhledejClick(Sender: TObject);
var
  Vysledek: TSearchRec;
  FileAttrs: Integer;

begin
  lbSoubory.Clear;
  if FindFirst(edtAdresar.Text, faAnyFile, Vysledek) = 0 then begin
    repeat
      if (Vysledek.Size div 1024) >= sedtVelikost.Value then
        lbSoubory.Items.Add(Vysledek.Name + ` - ` +
          IntToStr(Vysledek.Size div 1024) + ` kB`);
    until FindNext(Vysledek) <> 0;
    FindClose(Vysledek);
  end; // if
end;

procedure TfrmHlavni.btnKonecClick(Sender: TObject);
begin
  Application.Terminate;
end;

end.

Program funguje tak, že do editačního pole uživatel zadá cestu (a souborovou masku). Ve druhém editačním poli zadá (nebo vyhledá) velikost. Pokud vás např. zajímá, kolik dopisů z Wordu ve složce Dokumenty má velikost větší než 24 kB, zadejte c:\dokumenty\*.doc:

(obrázek 25-1.bmp)

Poznámka: třída TSearchRec, kterou při vyhledávání používáme, slouží právě funkcím FindFirst a FindNext k tomu, aby do ní uložily výsledky vyhledávání. V této struktuře (jde o záznam) se o každém nalezeném souboru ukládají tyto údaje: čas, velikost, atributy, název (a další údaje).

V následujících kapitolách se podíváme konkrétně na jednotlivé typy souborů a na specifika práce s nimi.

Soubory s udaným typem

Soubory s udaným typem (též typované soubory) se deklarují následujícím způsobem:

type NazevTypovanehoSouboru = file of type;

Typované soubory se používají, pokud dopředu přesně víme, jakou strukturu má soubor mít a jaká data bude uchovávat. Například pokud víme, že budeme ukládat pouze celá čísla, je výhodné nadeklarovat soubor celých čísel. Můžeme také mít deklarovanou složitou strukturu (např. záznam) a udělat soubor těchto záznamů:

type CelaCislaSoubor = file of Integer; // soubor celých čísel

type // typ záznam
  Clovek  = record
    Jmeno : string[20];
    Prijmeni : string[20];
    Vyska : integer;
    Vaha : integer;
    Vek : integer;
  end;

type // soubor těchto záznamů
  SouborLidi = file of Clovek;

Otevření typovaného souboru se provádí pomocí procedur Reset a Rewrite. Čtení a zápis se provádí pomocí procedur Read a Write. Pokud si typovaný soubor prohlédnete (např. klávesou F3 ve Windows Commanderu nebo otevřením v Poznámkovém bloku), nebudete z jeho obsahu příliš moudří – data nejsou ukládána v textové podobě a nejsou lidským okem rozluštitelná. (Mluvím-li o lidském oku, předpokládám, že majitelem příslušného oka je člověk náležející do průměrného vzorku populace, nikoliv průměrný čtenář Živě, jehož oko jistě obsah typovaného souboru hravě rozluští:-))

Pro netextové soubory (tedy pro typované a netypované) existuje ještě několik speciálních funkcí. Jejich shrnutí naleznete v tabulce za následující podkapitolou věnovanou souborům bez udaného typu.

Podívejte se na příklad. Následující aplikace obsahuje formulář (frmHlavni), dvě tlačítka (btnNacti a btnZapis) a jeden nápis (lblCisla). Po stisku tlačítka Zapis se do typovaného souboru celých čísel cisla.nr v aktuálním adresáři zapíše posloupnost čísel 1 až 10. Po stisku tlačítka Nacti se přečte celý obsah téhož souboru a vypíše se do nápisu lblCisla.

(obrázek 25-2.bmp)

Zdrojový text hlavního modulu příkladu:

unit Hlavni;

interface

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

type
  TfrmHlavni = class(TForm)
    btnZapis: TButton;
    btnNacti: TButton;
    lblCisla: TLabel;
    procedure btnZapisClick(Sender: TObject);
    procedure btnNactiClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  frmHlavni: TfrmHlavni;

implementation
type
  CelaCislaSoub = file of Integer;

var
  Soubor : CelaCislaSoub;

{$R *.DFM}

procedure TfrmHlavni.btnZapisClick(Sender: TObject);
var
  I : Integer;

begin
  AssignFile(Soubor, `cisla.nr`);
  Rewrite(Soubor);
  for I := 1 to 10 do
    Write(Soubor, I);

  CloseFile(Soubor);
end;

procedure TfrmHlavni.btnNactiClick(Sender: TObject);
var
  I : Integer;
begin
  lblCisla.Caption := ``;
  AssignFile(Soubor, `cisla.nr`);
  Reset(Soubor);
  while not Eof(Soubor) do begin
    Read(Soubor, I);
    lblCisla.Caption := lblCisla.Caption + IntToStr(I) + `, `;
  end;

  CloseFile(Soubor);
end;

end.

Soubory bez udaného typu

Soubory bez udaného typu (též netypované soubory) se deklarují následujícím způsobem, jen pomocí klíčového slova file:

var NetypovanySoubor: file;

Netypované soubory jsou nízkoúrovňové vstupně/výstupní (I/O) kanály. Primárně se používají k přímému přístupu k diskovým souborům. Pracuje se s nimi bez ohledu na jejich typ a strukturu.

V případě netypovaných souborů mají procedury Reset a Rewrite navíc jeden parametr, který udává velikost záznamu (bloku) používanou k přenosu (čtení / zápisu) dat. Tato velikost se udává v bytech. Z historických důvodů je přednastavená hodnota 128 bytů.

Při práci s netypovanými soubory jsou k dispozici všechny procedury a funkce používané při práci s typovanými soubory s výjimkou Read a Write. Místo nich se používají procedury BlockRead a BlockWrite. Tyto procedury jsou používány pro vysokorychlostní datové přenosy. Pokud si netypovaný soubor budete chtít prohlédnout, opět nebudete z jeho obsahu příliš moudří – data nejsou ukládána v textové podobě a nejsou lidským okem rozluštitelná.

Následující příklad ukazuje, jak lze netypovaných souborů použít ke kopírování souborů. Aplikace obsahuje kromě formuláře (frmHlavni) také jedno tlačítko (btnKopiruj) a dva dialogy: pro otevření souboru (dlgOpen) a pro uložení souboru (dlgSave). Po stisku tlačítka se nejprve otevře dialog pro otevření souboru, uživatel vybere existující soubor a poté se otevře dialog pro uložení souboru, ve kterém uživatel buď vybere existující soubor, nebo zadá nový soubor. Pak dojde ke zkopírování prvního vybraného souboru pod novým názvem. Zdrojový kód celého příkladu:

unit Hlavni;

interface

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

type
  TfrmHlavni = class(TForm)
    btnCopy: TButton;
    dlgOpen: TOpenDialog;
    dlgSave: TSaveDialog;
    procedure btnCopyClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

type
  NetypovanySoubor = file;


var
  frmHlavni: TfrmHlavni;

implementation

{$R *.DFM}

procedure TfrmHlavni.btnCopyClick(Sender: TObject);
var
  StarySoubor, NovySoubor : NetypovanySoubor;
  Buffer : array[1..1024] of char;
  Precteno, Zapsano: Integer;

begin
  if dlgOpen.Execute then begin
    AssignFile(StarySoubor, dlgOpen.FileName);
    Reset(StarySoubor, 1);
    if dlgSave.Execute then begin
      AssignFile(NovySoubor, dlgSave.FileName);
      Rewrite(NovySoubor, 1);
      Canvas.TextOut(100, 20, `Kopiruji ` + IntToStr(FileSize(StarySoubor))
        + ` bytů...`);
      repeat
        BlockRead(StarySoubor, Buffer, SizeOf(Buffer), Precteno);
        BlockWrite(NovySoubor, Buffer, Precteno, Zapsano);
      until (Precteno = 0) or (Zapsano <> Precteno);
      CloseFile(NovySoubor);
    end;
    CloseFile(StarySoubor);
  end;
end;
end.

(obrázek 25-3.bmp)

Pro netextové soubory (tedy typované a netypované) existuje ještě několik speciálních procedur a funkcí; shrneme si je v následující tabulce:

Procedura/Funkce Popis
function FilePos(var F): Longint; Vrací aktuální pozici v souboru. Přesněji řečeno vrací číslo právě zpracovávané komponenty (údaje) v souboru. Je-li aktuální pozice na začátku souboru, je vrácena nula, je-li aktuální pozice na konci souboru, je vrácena hodnota FileSize(F). Pozor: tyto údaje nemusí nutně korespondovat s velikostí souboru v bytech, neboť je vráceno číslo údaje a údaj může klidně zabírat více bytů (např. rozsáhlý záznam může zabírat 100 bytů nebo více).
function FileSize(var F): Integer; Vrací celkový počet údajů (záznamů, komponent, položek) v souboru. Nevrací velikost souboru v bytech*!
procedure Seek(var F; N: Longint); Nastavuje aktuální pozici v souboru na údaj (záznam, komponentu), jehož číslo je specifikováno parametrem N. Číslo první komponenty je 0, číslo poslední komponenty je FileSize(F) – 1.
procedure Truncate(var F); Vymaže všechny záznamy od aktuální pozice v souboru až do konce. Hodnota Eof(F) je následně True.
* - zkuste za domácí úkol vymyslet, v jakém případě bude hodnota vrácená funkcí FileSize rovna velikosti souboru v bytech.

Pozor: procedury a funkce uvedené v této tabulce nelze použít na textové soubory!

Textové soubory

Textové soubory se používají velmi často a ukládají se do nich nejen textové údaje, ale také čísla a další potřebné hodnoty. Deklarují se následujícím způsobem, pomocí klíčového slova text:

var TexttovySoubor: TextFile;

Poznámka:

Textové soubory je stejně dobře možné deklarovat pomocí klíčového slova Text, případně pomocí téhož klíčového slova uvozeného názvem jednotky System: System.Text.

Textové soubory jsou ideální v situaci, kdy potřebujeme údaje ukládat v určitém formátu a kdy chceme, aby vytvořený soubor byl „přenositelný“, tj. čitelný i mimo náš program libovolným editorem textů (Poznámkový blok). Údaje jsou do textového souboru ukládány znakově, výsledný soubor je tedy bez problémů čitelný. Navíc při ukládání můžeme bez problémů říci, co přesně (a v jakém formátu) chceme ukládat.

Základní složkou textového souboru je znak a textový soubor se dále člení na řádky. Řádkou textového souboru rozumíme posloupnost znaků ukončenou znakem konce řádky. Někdy (především z historických důvodů) můžete narazit také na členění na tzv. stránky. Stránka textového souboru je posloupnost znaků ohraničená znakem nové stránky (nebo určitý pevně stanovený počet řádků).

V kódu ASCII se používají následující řídicí znaky:

  • CR (Carriage Return, tzv. návrat vozíku) - #13;
  • LF (Line Feed, přechod na novou řádku) - #10;
  • FF (přechod na novou stránku) - #12.
Při běžné práci ovšem programátor tyto kódy nemusí nutně znát, neboť když už potřebuje např. testovat znak konce řádku, existuje pro to standardní funkce Eoln, viz níže.

Textové soubory se trochu jinak otvírají (viz výše). Procedura Reset otvírá soubor pouze pro čtení (při pokusu o zápis do takto otevřeného souboru dojde k chybě), procedura Append jej otvírá pro přípis (zápis za poslední položku) a procedura Rewrite zakládá nový soubor a otvírá jej pro zápis.

Při práci (pouze!) s textovými soubory máme k dispozici následující „speciální“ procedury a funkce:

Procedura/Funkce Popis
procedure AssignPrn(var F: Text); Tato procedura je deklarována v jednotce Printers. Přiřadí proměnnou typu textový soubor k tiskárně. Pokud budete standardně zapisovat nějaké údaje do takto otevřeného souboru, budou tyto údaje rovnou tištěny na tiskárnu.
function Eoln [(var F: Text) ]: Boolean; Funkce vrací hodnotu True, je-li aktuální pozice v souboru na znaku konce řádku.
procedure Flush(var F: Text); Zajistí, že všechny znaky textového souboru otevřeného pro zápis (nebo přípis), které jsme do něho zapisovali, v něm skutečně budou – dojde tedy k vyprázdnění souborového bufferu.
function SeekEof [ (var F: Text) ]: Boolean; Funkce podobná funkci Eof, vrací tedy True, pokud je aktuální pozice v souboru na znaku konce souboru. Vrací ale true také v případě, že do konce souboru zbývají již jen mezery, tabulátory a znaky konce řádku.
function SeekEoln [ (var F: Text) ]: Boolean; Funkce podobná funkci Eoln, vrací tedy True, je-li aktuální pozice v souboru na znaku konce řádky, ale také v případě, že do konce řádky zbývají již jen mezery nebo tabulátory.
procedure SetTextBuf(var F: Text; var Buf [ ; Size: Integer] ); Nastavuje vstupně/výstupní buffer textového souboru. Pokud nechceme používat interní buffer pro I/O operace, můžeme zde nastavit libovolnou proměnnou, která bude sloužit jako buffer místo interního. Lze též (nepovinně) nastavit velikost.

Podívejte se na příklad práce s textovým souborem. Následující modul vytvoří v aktuálním adresáři soubor udaje.txt a vypíše do něj 20 řádků následujícího tvaru:

Údaj č. 1: 10...KONEC

Celá akce je naprogramována jako obsluha události OnClick tlačítka btnStart hlavního formuláře frmHlavni.

unit Hlavni;

interface

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

type
  TfrmHlavni = class(TForm)
    btnStart: TButton;
    procedure btnStartClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

type
  TextovySoubor = Text;


var
  frmHlavni: TfrmHlavni;
  Soubor: TextovySoubor;

implementation

{$R *.DFM}

procedure TfrmHlavni.btnStartClick(Sender: TObject);
var
  I: Integer;

begin
  AssignFile(Soubor, `udaje.txt`);
  Rewrite(Soubor);
  for I := 1 to 20 do
    WriteLn(Soubor, `Údaj č. `, I, `: `, 10 * I, `...KONEC`);

  CloseFile(Soubor);
end;

end.

Pokud se následně podíváte v poznámkovém bloku do souboru udaje.txt, uvidíte zhruba takovýto obsah:

Údaj č. 1: 10...KONEC
Údaj č. 2: 20...KONEC
Údaj č. 3: 30...KONEC
Údaj č. 4: 40...KONEC
Údaj č. 5: 50...KONEC
Údaj č. 6: 60...KONEC
Údaj č. 7: 70...KONEC
Údaj č. 8: 80...KONEC
Údaj č. 9: 90...KONEC
Údaj č. 10: 100...KONEC
Údaj č. 11: 110...KONEC
Údaj č. 12: 120...KONEC
Údaj č. 13: 130...KONEC
Údaj č. 14: 140...KONEC
Údaj č. 15: 150...KONEC
Údaj č. 16: 160...KONEC
Údaj č. 17: 170...KONEC
Údaj č. 18: 180...KONEC
Údaj č. 19: 190...KONEC
Údaj č. 20: 200...KONEC

Samozřejmostí je, že pokud chceme z takovéhoto souboru číst, musíme přesně znát jeho strukturu (případně si ji průběžně „testovat“, ale to je poměrně pracné). Víme-li, že na řádce je nejprve výraz „Údaj č. „, pak číslo, dvojtečka, mezera, opět číslo, tři tečky a nápis „KONEC“, můžeme se souborem pracovat celkem bez problémů.

Důležitá poznámka

V žádném z předchozích příkladů není přítomno ošetřování chyb, ať již pomocí výjimek nebo funkce IOResult. Je to především z důvodu omezeného prostoru, nicméně v reálných aplikacích doporučuji ošetřit případné chyby u každé vstupně/výstupní operace, neboť práce se soubory (a se vstupem/výstupem obecně) patří k nejrizikovějším akcím vůbec.

Na závěr

Abych byl upřímný, původně jsem si myslel, že pojednání o souborech zabere pouze jednu kapitolu, a to se v ní ještě stihnu zmínit o třídách TRegistry, TIniFile a TRegistryIniFile. Bohužel, jak sami vidíte, rozsah problematiky souborů je takový, že její popis „nakynul“ na dva velmi rozsáhlé články. Nicméně nechtěl jsem vynechat žádnou důležitou informaci nebo funkci. Nyní tedy musíme téma souborů již uzavřít. Pokud byste přesto došli k závěru, že v dnešním a minulém článku chybělo něco klíčového, vyjádřete se prosím do diskuse (nebo mi napište mail).
Diskuze (3) Další článek: LCD monitor Lite-On pod dvacet

Témata článku: , , , , , , , , , , , , , , , , , , , , , , , , ,