Speicherung von Array/Records

Für Fragen zur Programmiersprache auf welcher Lazarus aufbaut
Antworten
e445697
Beiträge: 11
Registriert: Fr 22. Jun 2012, 11:46

Speicherung von Array/Records

Beitrag von e445697 »

HI Leute,
ich habe hier ein Problem. Ich habe die "Lösungen'' schon aus einem Forum genommen, jedoch habe ich es bisher nicht geschaft, den Quelltext so umzuschreiben, dass es bei mir 1. ohne Errors läuft geschwieige denn, das überhaupt etwas Funktioniert.

Folgender Quelltext:

Code: Alles auswählen

 
type      //Habe überlegt wegen Fehler bei der ganzzahligen Division mit einer Config Datei zu arbeiten, damit ich die länge des Arrays/ Records schon kenne beim Laden
 
    TConfig = record
      Leange:integer;
    end;
 
    type
      Config = array of  TConfig ; //Hier war meine überlegung einfach um nen Array zu speichern... das geht aber eig auch mit ner einfachen Textdatei (denke ich)
 
  type
    TRecord = record  //Das hier ist dass Hauptrecord , wellches in nem Array verwendet wird
      Name: string;
      Nummer: integer;
    end;
 
var
  Form1: TForm1;
  TArrayRec : Array of TRecord;
  TestArray : Array of TRecord;
type
 
  TDatei = class
    procedure SaveArray(const Pfad :string; const TArrayData : array of TRecord);
    procedure SaveConf(const Pfad :string; const RecordData: TConfig);
    procedure LoadArray(const AFilename: string; var TArrayData: TRecord);
    procedure LoadConf(const AFilename: string; var RecordData: Config);
  end;
var
  Datei: TDatei;
implementation
 
{$R *.lfm}
 
{ TForm1 }
 
{--------------------------Save Config------------------------------------}
procedure TDatei.SaveConf(const Pfad :string; const RecordData: TConfig);
var FStream: TFileStream;
  i : integer;
begin
  FStream := TFilestream.Create(Pfad, fmCreate); //öffnet den Stream, falls Pfad nicht vorhanden, wird er erzeugt
  try
      FStream.Write(RecordData.Leange, 5);
  finally
    FStream.Free;
  end;
end;
 
 
{--------------------------Save Array-------------------------------------}
procedure TDatei.SaveArray(const Pfad :string; const TArrayData : array of TRecord);
var FStream: TFileStream;
  i : integer;
  leange: integer;
begin
  FStream := TFilestream.Create(Pfad, fmCreate); //öffnet den Stream, falls Pfad nicht vorhanden, wird er erzeugt
  try
    for i := 0 to 3-1 do //Jedes einzelne Element per Stream in Datei Verfrachten
      FStream.Write(TArrayData[i], SizeOf(TArrayData[i]));
  finally
    FStream.Free;
  end;
end;
 
 
{--------------------------Lade Config-------------------------------------}
procedure TDatei.LoadConf(const AFilename: string; var RecordData: Config);   //Das is eher unwichtig, wie oben schon beschrieben
var
  FStream: TFilestream;
  i: Integer;
begin
  FStream := TFilestream.Create(AFilename, fmOpenRead); //Öffnet des Stream um Datei zu lesen
  try
      SetLength(RecordData, 1); //Stellt den Array auf die Datenmenge ein
      FStream.Read(RecordData[0].Leange, SizeOf(RecordData[Config[0].Leange-1])); //Einlesen der Datei
  finally
    FStream.free;
  end;
end;
 
 
{------------------------------Lade Array------------------------------------}
procedure TDatei.LoadArray(const AFilename: string; var TArrayData: TRecord); // Das hier ist das Laden, welches nicht funktioniert
var
  FStream: TFilestream;
  i: Integer;
begin
  FStream := TFilestream.Create(AFilename, fmOpenRead); //Öffnet des Stream um Datei zu lesen
  SetLength(TArrayData, 3); //Stellt den Array auf die Datenmenge ein
 
  try
    for i:= 0 to Length(TArrayData) do
            FStream.Read(TArrayData[i], SizeOf(TArrayData[i])); //Einlesen der Datei
  finally
    FStream.free;
  end;
end;
 
procedure TForm1.Button1Click(Sender: TObject); //Hier soll dann gespeichert werden
begin
  Datei.SaveConf('H:\Inf\Conf.dat', Config[0]);
  Datei.SaveArray('H:\Inf\Test.dat', TArrayRec);
end;
 
procedure TForm1.Button2Click(Sender: TObject); //und hier geladen
var i:integer ;
begin
  Datei.LoadConf('H:\Inf\Conf.dat',Conf2);
  Datei.LoadArray('H:\Inf\Test.dat',TestArray);
 
  for i:=0 to Length(TestArray) do
    begin
      ListBox1.Items.Add(TestArray[i].Name); //Und in ne Listbox eingetragen werden
    end;
end;
 
procedure TForm1.FormCreate(Sender: TObject); //Beispielwerte
begin
   SetLength(TArrayRec, 3);
   TArrayRec[0].Name:='Hans';
   TArrayRec[0].Nummer:=1;
   TArrayRec[1].Name:='Peter';
   TArrayRec[1].Nummer:=2;
   TArrayRec[2].Name:='Tom';
   TArrayRec[2].Nummer:=3;
   Config.Leange:=Length(TArrayRec);

Ich weiß, das es wahrscheinlich ziemlich unordentlich ist, da ich erstmal versucht habe selber des Problem (so halberfolgreich) zu lösen.
Und ich bin auf dem Gebiet der Speicherung ziemlich neu ^^
Also Entschuldigung für das Chaos und danke im Vorraus!

magnetron
Beiträge: 44
Registriert: Di 4. Nov 2014, 14:04

Re: Speicherung von Array/Records

Beitrag von magnetron »

Hallo e44,

mit FileStream sollte das ganz gut funktionieren.
Irgendwie kommt mir dies komisch vor:

Code: Alles auswählen

(RecordData[Config[0].Leange-1])
Eventuell solltest Du schauen, ob Deine Längen tatsächlich korrekt in Config gespeichert werden.

Eine Alternative wäre, alles in einen stream zu schreiben. Dann erhielte jeden Element einen "Header"
wir TConfig (oder nur einen Integer) wo die Länge (in bytes) drin steht.
Optional kann die ganze Datei noch einen globalen Header haben, wo Zusatzinfo drin steht,
in etwa so:

Code: Alles auswählen

THeader = record
  NumElements: Integer;
  Info: ShortString;
end;
Dann könntest Du in einen Stream schreiben:
<THeader><Integer><Data><Integer><Data> usw.

Wobei die Größe von THeader bekannt sein muß, die Größe von Integer eh bekannt ist
und nach lesen des Integers dadurch die Größe des nächsten Datenblocks bekannt ist.
Grüße, Stefan

Benutzeravatar
theo
Beiträge: 10857
Registriert: Mo 11. Sep 2006, 19:01

Re: Speicherung von Array/Records

Beitrag von theo »

Hab's jetzt nicht genau angeschaut, aber meistens ist das Problem, dass angenommen wird, die Grösse des Record würde sich mit der Länge des Strings (im Modus $H+) anpassen.
Das ist falsch. Bei AnsiString Modus ist die StringVariable immer einen Pointer gross und der Record speichert nur einen Pointer.
Nimm also ShortStrings (z.B. String[10]; ) oder speichere den String separat ab.

Warf
Beiträge: 2118
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: Speicherung von Array/Records

Beitrag von Warf »

Das Problem wie theo bereits erwähnt hat ist, dass AnsiStrings eine Referenz auf den eigentlichen String/Char Array sind.
Die Größe des Typen AnsiStrings ist demnach SizeOf(Pointer). Zum einen könntest du Fixe Strings verwenden (Shortstring, wenn 255 Zeichen zu viel sind dann auch String[MaxLen]) oder du Speicherst die Daten einzeln.

Ein Beispiel mit ShortStrings(Ich hab das jetzt sehr einfach gehalten, ohne Config etc):

Code: Alles auswählen

type
  TRecord = record
    Name: ShortString;
    Number: Integer;
  end;
 
TRecArr = array of TRecord;
 
implementation
 
procedure WriteRecArrToFile(FileName: String; recarr: TRecArr);
var fs: TFileStream;
  len: Integer;
begin
  fs:=TFileStream.Create(FileName, fmCreate);
  try
    len:=Length(recarr);
    fs.Write(len, SizeOf(len));
    fs.Write(recarr[0], len*SizeOf(TRecord));
  finally
    fs.Free
  end;
end;
 
procedure ReadRecArrFromFile(FileName: String; recarr: TRecArr);
var fs: TFileStream;
  len: Integer;
begin
  fs:=TFileStream.Create(FileName, fmOpenRead);
  try
    fs.Read(len, SizeOf(len));
    SetLength(recArr, len);
    fs.Read(recarr[0], len*SizeOf(TRecord));
  finally
    fs.Free
  end;
end;
 

Und hier mal ein Beispiel für die Daten einzeln zu Sichern:

Code: Alles auswählen

type
  TRecord = record
    Name: AnsiString;
    Number: Integer;
  end;
 
TRecArr = array of TRecord;
 
implementation
 
procedure WriteRecArrToFile(FileName: String; recarr: TRecArr);
var fs: TFileStream;
  len, i: Integer;
  procedure WriteStringToStream(str: AnsiString; s: TStream);
  var l: Integer;
  begin
    if Assigned(s) then
    begin
      l:=Length(str);
      s.Write(l, SizeOf(l));
      s.Write(str[1], l*SizeOf(AnsiChar));
    end;
  end;
 
begin
  fs:=TFileStream.Create(FileName, fmCreate);
  try
    len:=Length(recarr);
    fs.Write(len, SizeOf(len));
    for i:=0 to len-1 do
    begin
      WriteStringToStream(recarr[i].Name, fs);
      fs.Write(recarr[i].Number, SizeOf(Integer));
    end;
  finally
    fs.Free
  end;
end;
 
procedure ReadRecArrFromFile(const FileName: String; var recarr: TRecArr);
var fs: TFileStream;
  len, i: Integer;
  tmp: String;
  procedure ReadStringFromStream(var str: AnsiString; const s: TStream);
  var l: Integer;
  begin
    if Assigned(s) then
    begin
      s.Read(l, SizeOf(l));
      SetLength(str, l);
      s.Read(str[1], l*SizeOf(AnsiChar));
    end;
  end;
 
begin
  fs:=TFileStream.Create(FileName, fmOpenRead);
  try
    fs.Read(len, SizeOf(len));
    SetLength(recarr, len);
    for i:=0 to len-1 do
    begin
      ReadStringFromStream(tmp, fs);
      recarr[i].Name:=tmp;
      fs.Read(recarr[i].Number, SizeOf(Integer));
    end;
  finally
    fs.Free
  end;
end;  

e445697
Beiträge: 11
Registriert: Fr 22. Jun 2012, 11:46

Re: Speicherung von Array/Records

Beitrag von e445697 »

Ersteinmal vielen, vielen Dank!

Allerdings hätte ich noch kleine "Verständnisfragen"

Code: Alles auswählen

type
  TRecord = record
    Name: ShortString;
    Number: Integer;
  end;
 
TRecArr = array of TRecord;
 
implementation
 
procedure WriteRecArrToFile(FileName: String; recarr: TRecArr);
var fs: TFileStream;
  len: Integer;
begin
  fs:=TFileStream.Create(FileName, fmCreate);
  try
    len:=Length(recarr);
    fs.Write(len, SizeOf(len)); //Was hat es mit diesen beiden Zeilen genau auf sich? Ich vermute hier wird die Länge gespeichert?!?
    fs.Write(recarr[0], len*SizeOf(TRecord));//Wenn ja, dann warum hier mit dem Index 0 ?
  finally
    fs.Free
  end;
end;
 
procedure ReadRecArrFromFile(FileName: String; recarr: TRecArr);
var fs: TFileStream;
  len: Integer;
begin
  fs:=TFileStream.Create(FileName, fmOpenRead);
  try
    fs.Read(len, SizeOf(len)); //Das wäre hier das Selbe, aber das wird sich denke ich mal aus der Erklährung des oberen Teiles von selbst erklähren, da dies hier ja nur das abfragen des oberen eingeschirebenen ist
    SetLength(recArr, len);// 'Tschuldige für den haprigen Satzbau ^^
    fs.Read(recarr[0], len*SizeOf(TRecord));
  finally
    fs.Free
  end;
end;
 
Schonmal Danke im Vorraus!

magnetron
Beiträge: 44
Registriert: Di 4. Nov 2014, 14:04

Re: Speicherung von Array/Records

Beitrag von magnetron »

Hallo e44..

das geht in die Richtung, die ich oben vorschlug, also alles in einen Stream schreiben.
Wenn Du das analysierst, kannst Du es auf Deine Belange ausbauen.

Code: Alles auswählen

    len:=Length(recarr);
    fs.Write(len, SizeOf(len)); //Was hat es mit diesen beiden Zeilen genau auf sich? Ich vermute hier wird die Länge gespeichert?!?
    fs.Write(recarr[0], len*SizeOf(TRecord));//Wenn ja, dann warum hier mit dem Index 0 ?
len ist die Länge des array aus lauter TRecords, also zum Beispiel 3 Datensätze.
Dann wird zuerst die Anzahl, der folgenden Datensätze in den Stream geschrieben, also
die Variable len (ein Integer) mit soundsoviel byte (SizeOf(len)).
Dann werden die (im Beispiel 3) Datensätze in den Stream geschrieben, jeweils mit der Größe eines
TRecord (also len*SizeOf(TRecord)) beginnend mit Datensatz [0].
Der Shortstring hat eine feste Länge (siehe andere Kommentare), was weitere Komplikationen vermeidet.
Ich vergesse immer ob vor die Variable buffer (hier recarr[0]) ein @ muss oder nicht.
Weiterhin geht das vermutlich nur innerhalb Lazarus/fpc und ist mit Vorsicht zu geniessen
wenn man Sprach- und Plattformübergreifend arbeitet. In jedem Falle wird hier davon ausgegangen, dass
das array ab Element [0] als linearer Speicherbereich alloziert ist.

Wie auch immer funktioniert es. Und die fraglich Zeile mit recarr[0] schreibt tatsächlich alle
arrays (im Beispiel 3) in das file weil ja dort steht len*SizeOf(TRecord).
Damit werden auch die vorherigen Kommenatre klar, dass man wissen muss wie gross jeweils ein Element ist,
zum das später wieder auseinander zu bekommen.

Grüße, Stefan

Benutzeravatar
theo
Beiträge: 10857
Registriert: Mo 11. Sep 2006, 19:01

Re: Speicherung von Array/Records

Beitrag von theo »

@Warf: Deine Prozeduren ReadStringFromStream etc. sind überflüssig. Das kann der TStream von Haus aus:
http://www.freepascal.org/docs-html/rtl ... tring.html
http://www.freepascal.org/docs-html/rtl ... tring.html

e445697
Beiträge: 11
Registriert: Fr 22. Jun 2012, 11:46

Re: Speicherung von Array/Records

Beitrag von e445697 »

Hallo! Nochmal vielen Dank für die Zahlreichen Antworten!

Ich habe es jetzt an meinem gegebenem Beispiel hinbekommen einfache Arrays/Rec zu speichern.
Jedoch schaffe ich es nicht ganz, dies auf mein eigentliches "Projekt" zu übertragen, da dort die Strucktur "deutlich komplizierter" ist.

Hier meine Eigentliche Strucktur:

Code: Alles auswählen

TMedi = record           //Medikament
  Name:ShortString;           //Name des Medikaments
  Dose: Integer;         //Dose in mg
end;
 
TTageszeit = record           //Die Verschiedenen Tagszeitens
  Morgens : Array of TMedi;
  Mittags : Array of TMedi;
  Abends  : Array of TMedi;
  Nachts  : Array of TMedi;
end;
 
TWochentag = record           //Die verschiedene Wochentage
  Montag     : TTageszeit;
  Dienstag   : TTageszeit;
  Mittwoch   : TTageszeit;
  Donnerstag : TTageszeit;
  Freitag    : TTageszeit;
  Samstag    : TTageszeit;
  Sonntag    : TTageszeit;
end;
 
TPatienten = record        //Erstelle Record für Patienten
  Nachname: ShortString;        //Nachname
  Vorname:  ShortString;          //Vorname
  Nummer: integer;            //Patientennummer
  Wochentag: TWochentag;   //Medikamentenverteilung
end;                             
 
TPatienten_hilfe = array of TPatienten;
 
//und bei var steht noch
 
Patienten: Array of TPatienten;      //Erzäuge ein Array vom Typ TPatienten  
 
 
Dann habe ich noch das Speicher und Laden ein bisschen Ummodeliert

Code: Alles auswählen

procedure TDatei.WriteRecArrToFile(FileName: String; recarr: TPatienten_hilfe);   //Array in eine Datei Speichern
var fs: TFileStream;
  len: Integer;
begin
  fs:=TFileStream.Create(FileName, fmCreate);     //Stream erstellen
  try
    len:=Length(recarr);             //Länge feststellen
    fs.Write(len, SizeOf(len));      //Schreibe in die Datei
    fs.Write(recarr[0], len*SizeOf(Patienten));
  finally
    fs.Free
  end;
end;
 
function TDatei.ReadRecArrFromFile(FileName: String; recarr: TPatienten_hilfe):TPatienten_hilfe;
var fs: TFileStream;
  len: Integer;
begin
  fs:=TFileStream.Create(FileName, fmOpenRead);
  try
    fs.Read(len, SizeOf(len));
    SetLength(recArr, len);
    fs.Read(recarr[0], len*SizeOf(Patienten));
  finally
    fs.Free
  end;
 
  result:=recarr;
end; 
Nun ist es so, dass zwar das Programm "durchcompeliert" und auch beim eigentlichem Laden und Speichern keinen Fehler ausspuckt, nur wenn ich mir dann die Patienten in einer ListBox ausgeben lasse, sind weder die Werte für Vorname, Nachname, Nummer gesetzt noch die Medikamente/bzw die zugehörigen Dosen gesetzt.

Ich hoffe ich überrumpele euch nicht mit viiiieeel Code und großen Fragen :oops: :?:

Schonmal Danke im Vorraus :3

Warf
Beiträge: 2118
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: Speicherung von Array/Records

Beitrag von Warf »

Dein Problem sind die Dynamische Arrays in TTagesZeit, diese Bilden (genauso wie AnsiString) nur eine Referenz auf den eigentlichen Array (SizeOf(DynArray)=SizeOf(Pointer)). Entweder du nutzt Statische Arrays (Array[Low..High] of TMedi) oder du Speicherst alle daten Einzeln, funktioniert genauso wie mit den Strings (letztlich sind Dynamische Arrays und AnsiStrings ziemlich das Gleiche).

@Theo:
Interresant, wieder was gelernt

Michl
Beiträge: 2511
Registriert: Di 19. Jun 2012, 12:54

Re: Speicherung von Array/Records

Beitrag von Michl »

@e445697: Wenn ich mir so anschaue, was für Informationen du speichern möchtest, würde ich dir die Verwendung einer Datenbank (und wenn es SQLite ist) empfehlen. Das bedeutet zwar einiges an Einarbeitungszeit, bei Erweiterungen, Änderungen oder Auswertungen (mit entsprechenden Selects) wird dir eine Datenbank dann zum Vorteil.

Code: Alles auswählen

type
  TLiveSelection = (lsMoney, lsChilds, lsTime);
  TLive = Array[0..1] of TLiveSelection;  

Antworten