Na prednáške:
- práca so súbormi pomocou TStream,
- ukladanie objektov do súborov;
- čítanie a konštruovanie objektov uložených v súbore.
Stream (prúd)
Stream je lineárny súhrn údajov, ku ktorým zvyčajne
pristupujem sekvenčne - čiže ich postupne, od začiatku do konca čítame alebo
ukladáme. Pri práci so streamami využívame objektové triedy:
TStream
... základná trieda,
ktorá má abstraktné metódy pre zápis, čítanie;
TFileStream
... odvodená trieda pre prácu so
súbormi;
TMemoryStream
... stream v pamäti
(zapisuje/číta údaje do/z pamäte).
Ukážka - zápis čísel do súboru pomocou
TFileStream:
procedure Zapis;
var
S: TStream;
I: Integer;
begin
S:=TFileStream.Create('cisla.dat', fmCreate);
try
for I:=1 to 15 do
S.WriteBuffer(I, SizeOf(I));
finally
S.Free;
end;
end; |
Ukážka - čítanie čísel zo súboru pomocou
TFileStream:
procedure
Precitaj;
var
S: TStream;
I, Hod: Integer;
begin
S:=TFileStream.Create('cisla.dat', fmOpenRead);
try
for I:=1 to 15 do begin
S.ReadBuffer(Hod, SizeOf(Hod));
Form1.Memo1.Lines.Add(IntToStr(Hod));
end;
finally
S.Free;
end;
end; |
Používanie TMemoryStream
je veľmi podobné akurát, že pri vytváraní objektu nemusíme dopredu určiť
spôsob práce so streamom (netreba určovať, či ideme zapisovať alebo čítať
údaje).
Ukladanie objektov do súboru
Majme deklarované:
var
H: TSkelet;
S: TStream;
... tu nejako inicializujeme a
vytvoríme kostru (TSkelet)
Zlé príklady ukladania objektov:
- S.WriteBuffer(H, SizeOf(H)) ...
do súboru sa neuložia atribúty objekty ale iba adresa objektu
H, čiže smerník na miesto v pamäti, kde sa momentálne objekt
H nachádza;
- S.WriteBuffer(Pointer(H)^,
H.InstanceSize) ... takto uložíme atribúty objektu, ale aj tak
neodporúčam používať tento spôsob ukladania, pretože vôbec neuložíme celú
stromovú štruktúru (zoznam detí).
Čo by malo ukladanie a čítanie zabezpečovať:
- uloženie stavových premenných objektu - nemôžeme sa spoliehať na uloženie
celého pamäťového bloku, ktorý zodpovedá objektu;
- uloženie a zrekonštruovanie stromovej štruktúry - treba vedieť uložiť
zoznam detí a treba ho vedieť aj prečítať;
- objekty v strome sú rôzneho typu - pre každý objekt treba nejakým spôsobom
uložiť informácie o tom, akého typu je príslušný vrchol v stromu.
Problém je v tom, že do súboru nemôžeme ukladať adresy
objektov, pretože pri čítaní a konštruovaní môže byť pamäť inak obsadená a
objekty už s veľkou pravdepodobnosťou nebudú vytvorené na rovnakých adresách.
Preto musíme ukladanie atribútov objektu sami naprogramovať, pričom sa ale
musíme vyhnúť ukladaniu smerníkov na iné objekty:
type
TSkelet=class
private
...
protected
...
procedure
DoLoad(Stream: TStream); virtual;
procedure DoSave(Stream: TStream); virtual;
public
constructor Create(Parent: TSkelet); virtual;
...
class
procedure RegisterClass(Id: Cardinal);
class function GetClassFromId(Id: Cardinal):
TSkeletClass;
class function GetIdFromClass(ClassType:
TClass): Cardinal;
class function Load(Stream: TStream; Parent:
TSkelet): TSkelet;
procedure Save(Stream: TStream);
...
end; |
Potom zápis do súboru vyzerá nasledovne:
S:=TFileStream.Create('skelet.dat', fmCreate);
H.Save(S);
S.Free;
A čítanie zo súboru:
S:=TFileStream.Create('skelet.dat', fmOpenRead);
H:=TSkelet.Load(S, nil);
S.Free;
Implementácia:
var
ClassList: array of record
Id: Cardinal;
ClassType: TSkeletClass;
end;
class procedure TSkelet.RegisterClass(Id: Cardinal);
var
Index: Integer;
begin
Index:=Length(ClassList);
SetLength(ClassList, Index+1);
ClassList[Index].Id:=Id;
ClassList[Index].ClassType:=Self;
end;
class function TSkelet.GetClassFromId(Id: Cardinal): TSkeletClass;
var
Index: Integer;
begin
for Index:=0 to Length(ClassList)-1 do
if ClassList[Index].Id=Id then begin
Result:=ClassList[Index].ClassType;
Exit;
end;
Result:=nil;
end;
class function TSkelet.GetIdFromClass(ClassType: TClass): Cardinal;
var
Index: Integer;
begin
for Index:=0 to Length(ClassList)-1 do
if ClassList[Index].ClassType=ClassType then begin
Result:=ClassList[Index].Id;
Exit;
end;
Result:=0;
end;
class function TSkelet.Load(Stream: TStream; Parent: TSkelet):
TSkelet;
var
ClassType: TSkeletClass;
Id, Index, Count: Integer;
begin
Stream.ReadBuffer(Id, SizeOf(Id));
ClassType:=GetClassFromId(Id);
if ClassType=nil then begin
Result:=nil;
Exit;
end;
Result:=ClassType.Create(Parent);
try
Result.DoLoad(Stream);
Stream.ReadBuffer(Count, SizeOf(Count));
for Index:=0 to Count-1 do Load(Stream,
Result);
except
Result.Free;
raise;
end;
end;
procedure TSkelet.Save(Stream: TStream);
var
Id, Index, Count: Integer;
begin
Id:=GetIdFromClass(ClassType);
if Id=0 then Exit;
Stream.WriteBuffer(Id, SizeOf(Id));
DoSave(Stream);
Count:=ChildCount;
Stream.WriteBuffer(Count, SizeOf(Count));
for Index:=0 to Count-1 do
Childs[Index].Save(Stream);
end;
procedure TSkelet.DoLoad(Stream: TStream);
begin
Stream.ReadBuffer(FdX, SizeOf(FdX));
Stream.ReadBuffer(FdY, SizeOf(FdY));
Stream.ReadBuffer(FX, SizeOf(FX));
Stream.ReadBuffer(FY, SizeOf(FY));
end;
procedure TSkelet.DoSave(Stream: TStream);
begin
Stream.WriteBuffer(FdX, SizeOf(FdX));
Stream.WriteBuffer(FdY, SizeOf(FdY));
Stream.WriteBuffer(FX, SizeOf(FX));
Stream.WriteBuffer(FY, SizeOf(FY));
end;
...
initialization
TSkelet.RegisterClass(1);
end. |
V prípade, že sa v stromovej štruktúre nachádzajú objekty z
inej objektovej triedy, musíme tieto triedy zaregistrovať ešte predtým, ako
zavoláme metódy Load, alebo
Save. Novú triedu registrujeme podobne, ako
TSkelet, napríklad:
- TRubberSkelet.RegisterClass(2);
- TVrana.RegisterClass(3);
upravená trieda
TSkelet
© 2003
Ľubomír SALANCI