Avatar billede whynot Nybegynder
02. juni 2004 - 13:33 Der er 12 kommentarer og
1 løsning

gemme array of record vha. TFileStream

Hejsa

jeg har et "luksus" problem.. men det irriterer mig kolosalt at jeg ikke er i stand til at gøre min procedure mere smart.. så derfor poster jeg her mit problem.

jeg bruger TFileStream til at gemme et dynamisk array af record i en flad fil.

Anyway.. min procedure ser sådan her ud:

procedure TForm1.Button8Click(Sender: TObject);
var
  x: integer;
  gridarray: array of Tdeltur;
  i: Integer;
  Writer: TWriter;
  FileStream: TFileStream;
begin
  for x := 0 to 10 do
  begin
    setlength(gridarray, length(gridarray) + 1);
    gridarray[x].indhold := inttostr(x);
    gridarray[x].navn := 'TTextualColumn';
    gridarray[x].art := 'TTextualColumn';
  end;

  FileStream := TFileStream.Create('hejsa.txt', fmCreate);
  Writer := TWriter.Create(FileStream, $FF);
  Writer.WriteListBegin;
  for i := 0 to Length(gridarray) - 1 do
  begin
    Writer.WriteString(gridarray[i].indhold);
    Writer.WriteString(gridarray[i].navn);
    Writer.WriteString(gridarray[i].art);
    Writer.Write();
  end;

  Writer.WriteListEnd;
  FreeAndNil(Writer);
  FreeAndNil(FileStream);
end;

hvor Tdeltur er defineret som:

type
  Tdeltur = record
    indhold: string;
    art: string;
    navn: string;
  end;

som irriterer mig er at jeg skal løbe mit array igennem og write hvér string værdi fra hvér field.. det må og bør kunne gøres smartere og hurtigere..

jeg forstiller mig at jeg kan gøre i retningen af:

FileStream := TFileStream.Create('hejsa.txt', fmCreate);
  Writer := TWriter.Create(FileStream, $FF);
  Writer.WriteListBegin;
    Writer.Write(gridarray);
  Writer.WriteListEnd;
  FreeAndNil(Writer);
  FreeAndNil(FileStream);

...altså noget i retningen af når man gemmer en component på en gang.

jeg håber nogen kan hjælpe..
Avatar billede hrc Mester
02. juni 2004 - 18:40 #1
Nu ved jeg ikke hvor hurtigt der er behov for at gemme data, men dynamiske arrays er sikkert ikke det hurtigste...

Jeg havde selvfølgelig gået over åen efter vand og lavet en klasse á lá

TDataArts = (daTextualColumn,...);

TDataClass = class
private
  fIndhold : string;
  fArt : TDataArts;
  fNavn : string;
public
  constructor Create(const aArt : TDataArts; const aName, aIndhold : string);
  property Art : TDataArts read fArt; // r/o
  property Navn : string read fNavn; // ditto
  property Indhold : string read fIndhold write fIndhold;
  procedure SaveToStream(aStream : TStream);
end;

...

procedure TDataClass.SaveToStream(aStream : TStream);
var
  DataSize : integer;
begin
  DataSize := length(fNavn) + length(fIndhold) + sizeof(fArt) + 2
  aStream.Write(DataSize,sizeof(DataSize));
  aStream.WriteBuffer(pchar(fNavn)^,length(fNavn));
  aStream.WriteBuffer(pchar(fIndhold)^,length(fNavn));
  aStream.WriteBuffer(fArt,sizeof(fArt));
end;

Disse ting havde jeg liggende i en variant af en TList hvor der var en:

  SaveToStream(const aFileName : string);

der udførte følgende:

procedure TMyList.SaveToStream(const aFileName : string);
var
  i : integer;
  fs : TFileStream;
begin
  fs := TFileStream.Create(aFileName,fmCreate);
  try
    for i := 0 to Count - 1 do
      TDataClass(Items[i]).SaveToStream(fs);
  finally
    fs.Free;
  end;
end;


... og jeg beklager - det er ikke så kort som det du roder med, men jeg foretrækker klasser frem for dynamiske lister (og det er nok både uretfærdigt og konservativt). Synes det er genialt at kunne tilknytte handlinger (metoder) til de data man roder med, her SaveToStream.
En anden ting jeg fravalgte var at gemme i en tekstfil. Jeg valgte også at gå udenom TWriter (havde ikke set den før) idet jeg synes det er lettere at gemme på min måde. Det løser altså ikke dit problem, men kan måske give inspiration.
Avatar billede whynot Nybegynder
03. juni 2004 - 08:48 #2
jamen dog.. nej jeg synes du har ret - det er lettere at gå over floden efter vand - specielt når man programmerer... men jeg er ingen ørn og slet ikke programmør ;)

Så jeg må klare mig med hvad jeg kender og kan.

Jeg kan godt se at det er smart det som du forslår - jeg tror bare ikke at jeg kan styre det "skib" sikkert i havn.

Men jeg er lidt nysgerrig - fordi hvordan ser din variant af Tlist ud?..

Jeg har iøvrigt fundet ud af i mellemtiden at det slet ikke kan lade sige gøre at gemme et dynamisk array til en fil ... hvis man ønsker at gøre det som jeg lagde op til.

Det skyldes åbenbart at delphi allokerer ressourcer til arrayets elementer og hvis delphi ikke ved hvor store elementerne er, så kan delphi heller ikke håndtere at skulle give dem en ens artet struktur ved en "gemning til fil" procedure.

Løsningen på problemet er at man bruger statistiske arrays og definerer sine variable som f.eks. string[100].. altså strings med fast størrelse.. og det var jo ikke dette jeg var ude efter.!

Når jeg indsætter noget af din procedure i mit lille program.. får jeg "invalid typecast".
Avatar billede whynot Nybegynder
03. juni 2004 - 08:51 #3
jeg har nu følgende kode:

procedure TForm1.Button12Click(Sender: TObject);
var
  gridarray: Tgridarray;
  i: Integer;
  FileStream: TFileStream;
begin
  for i := 0 to 10 do
  begin
//    setlength(gridarray, length(gridarray) + 1);
    gridarray[i].indhold := inttostr(x);
    gridarray[i].navn := 'TTextualColumn';
    gridarray[i].art := 'TTextualColumn';
  end;

  FileStream := TFileStream.Create('hejsa.txt', fmCreate);
  for i := 0 to Length(gridarray) - 1 do
  FileStream.WriteBuffer(pchar(gridarray[i].indhold)^, length(gridarray[i].indhold));
  FileStream.WriteBuffer(pchar(gridarray[i].art)^, length(gridarray[i].art));
  FileStream.WriteBuffer(pchar(gridarray[i].navn)^, length(gridarray[i].navn));
  end;
  FreeAndNil(FileStream);
end.
Avatar billede whynot Nybegynder
03. juni 2004 - 08:51 #4
hvor og hvad gør jeg galt?
Avatar billede whynot Nybegynder
03. juni 2004 - 08:54 #5
iøvrigt.. hvorfor gemmer du:

  DataSize := length(fNavn) + length(fIndhold) + sizeof(fArt) + 2
  aStream.Write(DataSize,sizeof(DataSize));
Avatar billede hrc Mester
03. juni 2004 - 09:53 #6
DataSize skal jeg bruge når jeg indlæser data igen. Her får du det fulde eksempel. I bunden har jeg demonstreret hvordan man genererer og indlæser data fra listen.

uses
  SysUtils, ContNrs, Classes;

type
  TDataArts = (daTextualColumn); // One for now - more to come

  TDataClass = class
  private
    fIndhold : string;
    fArt : TDataArts;
    fNavn : string;
  public
    // Man kan oprette på to måder: Ved nyoprettelse bruges denne
    constructor Create(const aArt : TDataArts; const aNavn, aIndhold : string); overload;
    // Ved indlæsning bruger man denne
    constructor Create(aStream : TStream); overload;
    property Art : TDataArts read fArt; // read only
    property Navn : string read fNavn; // ditto
    property Indhold : string read fIndhold write fIndhold; // Indhold kan ændres
    procedure SaveToStream(aStream : TStream);
  end;

  TDataList = class(TObjectList)
  private
    function GetDataClass(aIndex: integer): TDataClass;
  public
    constructor Create; reintroduce;

    procedure SaveToStream(aStream : TStream);
    procedure SaveToFile(aFileName : string);

    procedure ReadFromStream(aStream : TStream);
    procedure ReadFromFile(aFileName : string);

    property Items[aIndex : integer] : TDataClass read GetDataClass; default;
  end;

implementation

{ TDataClass }

constructor TDataClass.Create(const aArt: TDataArts; const aNavn, aIndhold: string);
begin
  inherited Create;
  fArt := aArt;
  fNavn := aNavn;
  fIndhold := aIndhold;
end;

constructor TDataClass.Create(aStream: TStream);
var
  DataSize : integer;
  Buffer, p : pchar;
begin
{
Data ligger på følgende måde: DataSize (4 bytes), Navn med et #0 i enden, Indhold med et #0 i enden og Art (der vist fylder 1 byte
}

  aStream.Read(DataSize,sizeof(integer));
  GetMem(Buffer,DataSize); // Her bruger jeg DataSize
  try
    aStream.ReadBuffer(Buffer^,DataSize);
    p := Buffer; // Buffer rummer data, p er en pegepind der flytter rundt deri

    fNavn := p; // Pchars og strenge er kompatible
    p := StrEnd(p) + 1; // Ryk til næste streng i listen (fIndhold)

    fIndhold := p;
    p := StrEnd(p) + 1; // Ryk til næste element i listen (fArt)

    fArt := TDataArts(p^); // Tjek at den indlæses korrekt - er ikke helt sikker
  finally
    FreeMem(Buffer,DataSize);
  end; // try
end;

procedure TDataClass.SaveToStream(aStream: TStream);
var
  DataSize : integer;
begin
  DataSize := length(fNavn) + length(fIndhold) + sizeof(fArt) + 2;
  aStream.Write(DataSize,sizeof(integer));
  aStream.WriteBuffer(pchar(fNavn)^,length(fNavn) + 1);
  aStream.WriteBuffer(pchar(fIndhold)^,length(fNavn) + 1);
  aStream.WriteBuffer(fArt,sizeof(fArt));
end;

{ TDataList }

constructor TDataList.Create;
begin
  // True sikrer at når listen frigives, så frigives elementerne også
  inherited Create(true);
end;

function TDataList.GetDataClass(aIndex: integer): TDataClass;
begin
  result := TDataClass(inherited Items[aIndex]);
end;

procedure TDataList.ReadFromFile(aFileName: string);
var
  fs : TFileStream;
begin
  fs := TFileStream.Create(aFileName,fmOpenRead);
  try
    ReadFromStream(fs);
  finally
    fs.Free;
  end;
end;

procedure TDataList.ReadFromStream(aStream: TStream);
begin
  while aStream.Position <> aStream.Size do
    Add(TDataClass.Create(aStream));
end;

procedure TDataList.SaveToFile(aFileName: string);
var
  fs : TFileStream;
begin
  fs := TFileStream.Create(aFileName,fmCreate);
  try
    SaveToStream(fs);
  finally
    fs.Free;
  end;
end;

procedure TDataList.SaveToStream(aStream: TStream);
var
  i : integer;
begin
  for i := 0 to Count - 1 do
    Items[i].SaveToStream(aStream);
end;

------------------------- o -------------------------

I min form har jeg to knapper:

procedure TfrmMain.Button1Click(Sender: TObject);
var
  i : integer;
  DataList : TDataList;
begin
  DataList := TDataList.Create;
  try
    for i := 0 to 99 do
      DataList.Add(TDataClass.Create(daTextualColumn,'Hans','Hedtoft'));
    DataList.SaveToFile('c:\data.dat');
  finally
    DataList.Free;
  end;
end;

procedure TfrmMain.Button2Click(Sender: TObject);
var
  i : integer;
  DataList : TDataList;
begin
  DataList := TDataList.Create;
  try
    DataList.ReadFromFile('c:\data.dat');
    for i := 0 to DataList.Count - 1 do
      ListBox1.Items.Add(DataList[i].Navn);
  finally
    DataList.Free;
  end;
end;
Avatar billede whynot Nybegynder
03. juni 2004 - 10:36 #7
hejsa

det ser ud til at det med at gemme til en fil går fino i din procedure.. men når jeg prøver at hente igen vha.

procedure TDataList.ReadFromFile(aFileName: string);
var
  fs : TFileStream;
begin
  fs := TFileStream.Create(aFileName,fmOpenRead);
  try
    ReadFromStream(fs);
  finally
    fs.Free;
  end;
end;

så giver
ReadFromStream(fs); fejlen : out of memory.!??!

øhmmm hvad skyldes det?
Avatar billede hrc Mester
03. juni 2004 - 12:40 #8
Det skyldes denne linie (som mangler):

aStream.WriteBuffer(pchar(fIndhold)^,length(fIndhold) + 1);

Den rettede procedure ser sådan ud:

procedure TDataClass.SaveToStream(aStream: TStream);
var
  DataSize : integer;
begin
  DataSize := length(fNavn) + length(fIndhold) + sizeof(fArt) + 2;
  aStream.Write(DataSize,sizeof(integer));
  aStream.WriteBuffer(pchar(fNavn)^,length(fNavn) + 1);
  aStream.WriteBuffer(pchar(fIndhold)^,length(fIndhold) + 1); // Her var fejlen
  aStream.WriteBuffer(fArt,sizeof(fArt));
end;

Beklager
Avatar billede borrisholt Novice
04. juni 2004 - 10:28 #9
whynot>> Beklager at bruge dit spørgsmål til dette.

hrc>> Du plejer gerne at ville lære et nyt haj trick så hold øje med det her spm :
http://www.eksperten.dk/spm/505823

Jens B
Avatar billede whynot Nybegynder
14. juni 2004 - 09:16 #10
hey no probs borrisholt :)

iøvrig vil jeg gerne undskylde at jeg ikke har vendt tilbage før nu.. men jeg har været alvorlig syg så computere og mine programmeringshobby har haft meget lav priotet.

... men det virker nu - dejligt :)

...men jeg har lige et supplerende spørgsmål til dig hrc.. når nu jeg er gået over til din måde at tænke på.. så kommer jeg til at tænke på super og sup klasser.

Det er noget med at man kan bruge samme funktion til at gemme forskellige lister - f.eks. liste_kunder og liste_ordre osv. Men ved at have "gemmeproceduren" i superklassen og de enkelte oplysninger/egenskaber for en liste i en supklasse.

Er jeg helt galt på den?.. eller er det ikke måden at gøre det på?.. i så fald hvordan pokker fungerer det så - hvis vi tager udgangspunkt i dit eget eksempel?.
Avatar billede hrc Mester
14. juni 2004 - 12:51 #11
Jeg skal ned og have kulhydrater, proteiner og ikke for meget fedt indenbors, så det bliver lidt kort...

TDataClass kunne nedarve fra en TBaseDataClass á lá:

TBaseDataClass = class
  private
    fArt : TDataArts;
    fNavn : string;
  protected
    property Art : TDataArts read fArt; // read only
    property Navn : string read fNavn; // ditto
  public
    // Man kan oprette på to måder: Ved nyoprettelse bruges denne
    constructor Create(const aArt : TDataArts; const aNavn : string); overload;
    // Ved indlæsning bruger man denne
    constructor Create(aStream : TStream); virtual; overload;
    procedure SaveToStream(aStream : TStream); virtual;
  end;

Jeg har udskildt fællesnævnerne (Art og Navn) - en optimering som man kan diskutere hører hjemme i base-klassen eller måske i en klasse midt imellem (TBaseArtKlasse?), men man ender sgu tit med en hel række af klasser - og der løber jeg lidt sur i OOP'en.

Den ene constructor bliver virtual så den må overrides af en nedarvende klasse (men kaldes jo via "inherited"). Det samme gælder SaveToStream;

De to properties bliver "protected" det betyder at de nedarvende klasser bliver nødt til at skrive "Property Art" i deres public-sektion for at de bliver synlige - kan sige at det er ret optionelt.

I TDataListen skal TDataClass erstattes med TBaseDataClass, men det er vist også alt der.
Avatar billede hrc Mester
14. juni 2004 - 13:28 #12
Håber i øvrigt at du er kommet dig godt. Mht. gemmeproceduren, som du nævner, så er den ikke 100% placeret i superklassen sådan som jeg har lavet de her - nærmere splittet op. Jeg tror ikke det kan være anderledes for en superklasse kender ikke sine sub-klasser.

Når du fra TDataListen indlæser må listen sørge for at oprette de rigtige data-klasser, mens det at gemme:

procedure TDataList.SaveToStream(aStream: TStream);
var
  i : integer;
begin
  for i := 0 to Count - 1 do
    Items[i].SaveToStream(aStream);
end;

er uændret idet den rigtige gemme-metode automatisk vil blive kaldt. Tumler lidt med om det kan laves smartere - måske dukker der noget op (men nu må jeg altså igang med at tjene penge igen).

I øvrigt vil Art endda være unødvendig hvis hver nedarvede klasse håndterer sin specifikke Art - så kunne man spørge på klassen if (theClass is TOpelClass) then, men det vil være usmart i forbindelse med at bruge "case": case theClass.Art of ...
Avatar billede whynot Nybegynder
14. juni 2004 - 16:21 #13
Mange tak for hjælpen og den lille guide.. dejligt :)

Jeg vil prøve at grave dybere i emnet - så det kan jo være at vi støder på hinanden igen på eksperten ;)
Avatar billede Ny bruger Nybegynder

Din løsning...

Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.

Loading billede Opret Preview
Kategori
Kurser inden for grundlæggende programmering

Log ind eller opret profil

Hov!

For at kunne deltage på Computerworld Eksperten skal du være logget ind.

Det er heldigvis nemt at oprette en bruger: Det tager to minutter og du kan vælge at bruge enten e-mail, Facebook eller Google som login.

Du kan også logge ind via nedenstående tjenester