26. Binárne súbory


posledná zmena: 14.2.2003

Banner Text 11.2.2003

    čo sme sa doteraz naučili

    • textové súbory:
      • postupnosť znakov, ktorá môže obsahovať špeciálny znak konca riadka - je riadkovo orientovaný
      • čítať sa dá len postupne od začiatku do konca; podobne, zapisovať sa dá len na koniec súboru (sekvenčný prístup)
      • pre čítanie sa dá nastaviť na začiatok súboru
      • automatické konverzie pri čítaní/zapisovaní iných typov ako znak
      • pri otvorení súboru musíme určiť, či je súbor len na zápis alebo len na čítanie

    čo sa budeme dnes učiť

    • dva typy binárnych súborov: typové a netypové
    • údajový prúd (stream)

Typy súborov

V Delphi môžeme so súbormi pracovať 3 rôznymi spôsobmi:

  • starý štýl (z Turbo Pascalu) - sem patrí aj TextFile
  • Windows úroveň práce so súbormi (najnižšia úroveň) - Delphi ju využívajú pre svoj súborový typ
  • Delphi štýl - trieda TStream = údajový prúd

Najprv sa zoznámime so starým štýlom, potom prejdeme na nový štýl - prúdy

Binárne súbory

  • postupnosť viet buď rovnakého typu alebo rôznych typov -- hovoríme o súboroch s pevnou a premenlivou dĺžkou viet
  • jednou operáciou Read alebo Write môžeme naraz prečítať alebo zapísať viac viet
  • zo súboru môžeme súčasne čítať aj do neho zapisovať, bez toho, aby sme museli niečo prepínať
  • hocikedy môžeme určiť presné miesto v súbore (pozíciu), odkiaľ budeme čítať, resp. kam budeme zapisovať (priamy prístup)
  • pozícia v súbore sa po každej operácii čítanie/zápis automaticky posúva na nasledujúcu vetu (môžeme so súborom pracovať sekvenčne)

v Pascale sú to dva typy práce s binárnymi súbormi:

  • typové binárne súbory -- už pri deklarovaní súboru určíme typ vety a tým aj dĺžku vety -- do súboru môžeme zapisovať len vety tohto typu a tiež z neho čítať len do premenných tohto typu -- pozícia vety v súbore sa vyjadruje počtom viet, ktoré sú pred danou vetou (číslovanie od 0 do FileSize-1)
  • netypové binárne súbory -- môžeme do neho zapisovať ľubovoľné údaje (premenné) a tiež z neho čítať do premenných ľubovoľných typov -- pri otváraní musíme určiť veľkosť elementárneho bloku a potom všetky pozície v súbore a dĺžky budú určované v týchto blokoch (default je 128)

Typové binárne súbory

type súbor = file of identifikátor_typu;     // typ vety
  • POZOR - veta nesmie obsahovať žiadne časti s nie pevnou dĺžkou, napr. dynamické polia, veľké stringy, súbory, smerníky a iné dynamické premenné (môže obsahovať jednoduché premenné, polia, množiny, záznamy, krátke reťazce - string[...])
  • AssignFile, Reset, Rewrite, CloseFile - rovnaké ako pre textové súbory
  • Read, Write - majú trochu iné pravidlá, napr. pre
    • var f:subor;
      ...
      Read(f, premenná_typu_veta_súboru);
      Write(f, premenná_typu_veta_súboru);
      // pre read aj write môže byť aj viac premenných oddelených čiarkami
  • Eof(f) - ako pre textový súbor
  • FileSize(f) - počet viet súboru
  • FilePos(f) - číslo momentálne spracovávanej vety (pozícia v súbore) - číslujeme od 0
  • Seek(f,číslo); ( 0 <= číslo <= filesize(f) ) - nastavenie aktuálnej vety
    • čítanie za koncom súboru - napr. pre seek(f,filesize(f)); read(f,prem); => chyba (program spadne)
    • ale zápis za koniec súboru - napr. pre seek(f,filesize(f)); write(f,prem);  => je OK
  • Truncate(f); - momentálne nastavená veta a všetky vety za ňou sa zo súboru zrušia  (t.j. všetky vety od filepos(f))
    • Rewrite(f) = seek(f,0); truncate(f)

príklad:

var
  f:File of integer;
  i,j:integer;
begin
  AssignFile(f,'cisla.dat'); Rewrite(f);
  for i:=1 to 20 do Write(f,i);   // 80 bajtov
// CloseFile(f); - netreba, ak nasleduje Reset
  Reset(f);    // čítanie od začiatku
  for i:=1 to 20 do begin
    Read(f,j);
    Memo1.Lines.Add(IntToStr(j)); // sekvenčné čítanie
  end;
// slušnejšie - po koniec súboru:
  Reset(f);                      // alebo seek(f,0);
  while not Eof(f) do begin
    Read(f,j);
    Memo1.Lines.Add(IntToStr(j));
  end;
// nesmie sa použiť readln ani writeln - neexistuje značka konca riadka
// výpis súboru odzadu:
  for i:=FileSize(f)-1 downto 0 do begin
    Seek(f,i); Read(f,j);        // priame čítanie
    Memo1.Lines.Add(IntToStr(j));
  end;
// pridaj vetu na koniec súboru:
  Seek(f,FileSize(f));
// hoci sa ako posledný robil reset, nevadí to a do súboru sa dá aj zapisovať
//  - reset a rewrite sa navzájom neblokujú => každý otvorený súbor je
//    automaticky aj na čítanie aj na zápis
  for i:=21 to 40 do Write(f,i);
// skracovanie súboru
  Seek(f,25); Truncate(f);    // súbor má teraz vety 0..24
  CloseFile(f);
end;

procedúra vyhod vyhodí í-tu vetu tak, že ju nahradí poslednou a poslednú zruší:

type
  veta = integer;
  TSubor = file of veta;
...
  procedure vyhod(var f:TSubor; i:integer);
  var
    v:veta;
  begin
    Seek(f,FileSize(f)-1); read(f,v);    // načítanie poslednej
    Seek(f,i); write(f,v);               // zápis ako i-tej
    Seek(f,FileSize(f)-1); Truncate(f);  // vyhodenie poslednej
  end;
...
  vyhod(f,3); vyhod(f,10); vyhod(f,17);  // veta na pozícii 3 obsahuje číslo 4,...

  Memo1.Lines.Add('po vyhodení 3,10,17');
  Reset(f);
  while not Eof(f) do begin
    Read(f,j);
    Memo1.Lines.Add(IntToStr(j));
  end;

Takýto príklad bol kedysi na skúške:

    V binárnom súbore (file of integer) sú čísla, ktoré popisujú nakreslenie binárneho stromu. Súbor obsahuje bloky = štvorice celých čísel x, y, l, p, ktoré popisujú vrcholy stromu: x a y sú súradnice vrcholu, l a p sú čísla blokov (štvoríc) v súbore, ktoré sú spojené s daným vrcholom. 0-tý blok je trojica x, y, v, kde v je číslo toho bloku (štvorice), v ktorom sa nachádza nasledujúci vrchol v súbore. Všetky ostatné bloky sú už štvorice. Ak je nejaké číslo bloku (štvorice) menšie ako 0, tak sa predpokladá, že taká štvorica už neexistuje.

časť riešenia:

var
  f:file of integer;
  c:TCanvas;

procedure strom(x0,y0,v:integer);
var
  x,y,l,p:integer;
begin
  if v<0 then exit;
  seek(f,v*4-1);
  read(f,x,y,l,p);
  c.MoveTo(x0,y0);
  c.LineTo(x,y);
  strom(x,y,l);
  strom(x,y,p);
end;

procedure TForm1.Button1Click(...);
var
  x,y,v:integer;
begin
  c:=Image1.Canvas;
  AssignFile(f,'strom.dat'); Reset(f);
  read(f,x,y,v);
  strom(x,y,v);
  CloseFile(f);
end;

pre typové binárne súbory sa automaticky prepočítava Seek, FilePos, FileSize podľa dĺžky vety, napr.

  • Seek(f,číslo) - nastaví sa v súbore na bajt číslo*dĺžka_vety
  • FileSize(f)     = počet_bajtov_súboru div dĺžka_vety

Netypový binárny súbor

    var f:file;
  • Pascalu neprezradíme typ vety, teda nerobí sa žiadna kontrola na typ zapisovanej/čítanej premennej
  • nefunguje read, write - musí sa to robiť inak:
    BlockRead(var F: File; var Buf; pocet: integer [; var vysledok:integer]);
    • F - premenná pre netypový súbor
    • Buf - nejaká premenná (netypový parameter, znamená ľubovoľný typ)
    • pocet - počet čítaných blokov, typu integer
    • vysledok - skutočne načítaný počet blokov
      - ak sa tento parameter neuvedie a nastane nejaká chyba, tak program spadne na vstupno-výstupnej chybe (inak pokračuje a môžeme otestovať, či nastala chyba)
  • BlockWrite(var f: File; var Buf; pocet: integer [; var vysledok:integer]);
    • F - premenná pre netypový súbor
    • Buf - nejaká premenná (ľubovoľného typu)
    • pocet - výraz udávajúci počet zapísaných blokov, typu integer
    • vysledok - skutočne zapísaný počet blokov (rovnako ako pre BlockRead)
  • Reset(f,dĺžka_bloku); Rewrite(f,dĺžka_bloku);
    • ak sa neuvedie dĺžka_bloku, tak sa predpokladá 128 !!!
    • akoby v typových súboroch bolo Reset(f,sizeof(veta))
  • Seek, FilePos, FileSize - pracujú v závislosti na dĺžke bloku

Príklad:

kopírovanie súboru pomocou typového súboru:

var
  a,b:file of byte;
  x:byte;
begin
  AssignFile(a,'a.dat'); Reset(a);
  AssignFile(b,'b.dat'); Rewrite(b);
  while not Eof(a) do begin
    Read(a,x);
    Write(b,x)
  end;
  CloseFile(a);
  CloseFile(b);
end;

kopírovanie pomocou netypového súboru - po jednom bajte:

var
  a,b:file;
  x:byte;
begin
  AssignFile(a,'a.dat'); Reset(a,1);   // 1 bajt = veľkosť bloku
  AssignFile(b,'b.dat'); Rewrite(b,1);
  while FilePos(a)<FileSize(a) do begin    // not Eof(a)
    BlockRead(a,x,1);
    BlockWrite(b,x,1);
  end;
  CloseFile(a);
  CloseFile(b);
end;

kopírovanie pomocou netypového súboru - použitím pomocnej pamäte:

const
  dlzka = 32768;
var
  a,b:file;
  x:array [0..dlzka-1] of byte; // 32 kB buffer = krajšie by bolo pomocou pointra
  da,db:integer;
begin
  AssignFile(a,'a.dat'); Reset(a,1);   // 1 bajt = veľkosť bloku
  AssignFile(b,'b.dat'); Rewrite(b,1);
  repeat
    BlockRead(a,x,dlzka,da);
    BlockWrite(b,x,da,db)
  until (da=0) or (da<>db);            // ak da<>db tak nastala chyba pri zápise
  if da>0 then ShowMessage('chyba pri zápise');
  CloseFile(a);
  CloseFile(b);
end;

Vlastná trieda TSubor

vlastná triedu, ktorá "obalí" netypový súbor:

type
  TSubor = class
  private
    f:file;
    function getpos:integer;
    procedure setpos(p:integer);
    function getsize:integer;
    procedure setsize(p:integer);
  public
    constructor Create(m:string; novy:boolean);
    destructor Destroy; override;
    procedure read(var b; d:integer); virtual;
    procedure write(var b; d:integer); virtual;
    property position:integer read getpos write setpos;
    property size:integer read getsize write setsize;
  end;

constructor TSubor.Create(m:string; novy:boolean);
begin
  AssignFile(f,m);
  if novy then Rewrite(f,1) else Reset(f,1);
end;

destructor TSubor.Destroy;
begin
  CloseFile(f);
end;

procedure TSubor.read(var b; d:integer);
begin
  BlockRead(f,b,d);
end;

procedure TSubor.write(var b; d:integer);
begin
  BlockWrite(f,b,d);
end;

function TSubor.getpos:integer;
begin
  Result:=FilePos(f);
end;

procedure TSubor.setpos(p:integer);
begin
  Seek(f,p);
end;

function TSubor.getsize:integer;
begin
  Result:=FileSize(f);
end;

procedure TSubor.setsize(p:integer);
var
  pp:integer;
begin
  pp:=FilePos(f);
  Seek(f,p); Truncate(f);
  if pp>p then pp:=p;
  seek(f,pp);
end;

a môžeme to použiť:

var
  f:TSubor;
  i:integer;
begin
  f:=TSubor.Create('subor.dat',true);
  try
    for i:=1 to 20 do
      f.write(i,sizeof(i));
    f.position:=0;
    while f.position < f.size do begin       // eof
      f.read(i,sizeof(i));
      Memo1.Lines.Add(inttostr(i));
    end;
  finally
    f.Free;                  // namiesto CloseFile
  end;
end;

Dátový prúd - trieda TStream

V Delphi exituje základná trieda Tstream, ktorá zovšeobecňuje metódy na zapisovanie a čítanie údajov z nejakého záznamového média.  Táto trieda zakryje implementáciu fyzického súboru.

TStream je "abstraktná" trieda - pracujeme buď s jej konkrétnymi podtriedami, napr. TFileStream, alebo si zrealizujeme vlastnú podtriedu. TStream uvedené metódy (abstract) nemá definované len deklarované.

vlastnosti (property):

  • Position
  • Size

metódy:

  • CopyFrom(Source:TStream; Count:integer):integer;
  • Read(var Buffer; Count:integer):integer;
  • ReadBuffer(var Buffer; Count:integer);  -- chyba, ak sa nedá
  • Seek(Offset:integer; Origin:Word):integer;
    • kde Origin == soFromBeginning, soFromCurrent, soFromEnd
  • Write(const Buffer; Count:integer):integer;
  • WriteBuffer(const Buffer; Count:integer);

dôležité sú odvodené triedy, napr.

  • TFileStream
    • constructor Create(const FileName:string; Mode:Word);
    • kde Mode:
      • fmCreate - vytvorí súbor
      • fmOpenRead - len čítanie
      • fmOpenWrite - len zápis
      • fmOpenReadWrite - aj zápis aj čítanie
  • TMemoryStream
    • LoadFromFile(const FileName:string);
    • LoadFromStream(Stream:TStream);
    • SaveToFile(const FileName:string);
    • SaveToStream(Stream:TStream);

a posledný príklad môžeme prepísať pomocou TStreamu takto:

var
  f:TStream;
  i:integer;
begin
  f:=TFileStream.Create('subor.dat',fmCreate);
  try
    for i:=1 to 20 do
      f.write(i,sizeof(i));
    f.position:=0;
    while f.position < f.size do begin
      f.read(i,sizeof(i));
      Memo1.Lines.Add(inttostr(i));
    end;
  finally
    f.Free;
  end;
end;


© 2003 AB, KVI
blaho@fmph.uniba.sk