filestream laden/speichern

Für Fragen von Einsteigern und Programmieranfängern...
Antworten
Hartkern
Beiträge: 69
Registriert: Sa 5. Dez 2015, 20:03
OS, Lazarus, FPC: Win10 IDE 1.6
CPU-Target: 64Bit
Wohnort: Leipzig

filestream laden/speichern

Beitrag von Hartkern »

Hallo,
ich versuche Datensätze, die in einer ObjectList schlummern und lediglich je aus einem 1 String und 2 Integerwerte per Filestream zu speichern und anschließend wieder aus dem Filestream laden und die Objectliste mit den minimal befüllten Objekten zu befüllen.

Nur hab ich den Eindruck das irgendwas nicht wirklich funktioniert beim Laden.

Mein Ansatz für das speicher

Code: Alles auswählen

procedure TForm2.ListeSpeichern;
var
  Zieldaten: TFileStream;
  Groesse : Integer;
  I : Integer;
  Anzahl : Integer;
begin
 
  Zieldaten := TFileStream.Create('Basis.dat', fmCreate);
  try
      //Anzahl vorhandener Datensätze in Liste als 1. Wert der Datei schreiben
      Zieldaten.Position :=0;
      Anzahl:=lBasisListe.Count-1;
      Zieldaten.WriteBuffer(IntToStr(Anzahl),Length(IntToStr(Anzahl)));
      //Datensätze schreiben
      For I:=0 to Anzahl do begin
          BasisEinheit := lBasisListe.Items[i] as TBasisEin;
          Zieldaten.Position := i+1;
          //Name      (ist ein String)
          Zieldaten.WriteBuffer(BasisEinheit.Name,Length(BasisEinheit.Name));
          //Maximale Felder pro Runde  (Ist ein Integerwert)
          Zieldaten.WriteBuffer(IntToStr(BasisEinheit.MaxBewegung),BasisEinheit.MaxBewegung);
          //Maximale Aufklärungsreichweite  (Ist ein Integerwert)
          Zieldaten.WriteBuffer(IntToStr(BasisEinheit.MaxRecon),BasisEinheit.MaxRecon);
    end;
 finally
    FreeAndNil(Zieldaten);
  end;
 
end;                               
und hier das laden

Code: Alles auswählen

procedure TForm2.ListeLaden;
var
  Zieldaten: TFileStream;
  I,C : Integer;
  s : String;
  Anzahl : Integer;
 
begin
  Zieldaten := TFileStream.Create('Basis.dat', fmOpenRead);
  Try
      //Anzahl der Datensätze lesen
      Zieldaten.Position :=0;
      Zieldaten.ReadBuffer(Anzahl,SizeOf(Anzahl));
      //Datensätze lesen und Objecte und die Objectliste damit befüllen
      For I:=0 to Anzahl-1 do
      begin
            Zieldaten.Position := i;
            BasisEinheit := TBasisEin.create;
            //Name     (ist ein String)
            Zieldaten.ReadBuffer(s,SizeOf(BasisEinheit.Name));
            BasisEinheit.Name:=S;
            //Maximale Felder pro Runde   (Ist ein Integerwert)
            Zieldaten.ReadBuffer(c,SizeOf(BasisEinheit.MaxBewegung));
            BasisEinheit.MaxBewegung:=c ;
            //Maximale Aufklärungsreichweite  (Ist ein Integerwert)
            Zieldaten.ReadBuffer(c,SizeOf(BasisEinheit.MaxRecon));
            BasisEinheit.MaxRecon:=c;
            //lBasisListe mit Object füllen
            lBasisListe.Add(BasisEinheit);
      end;
  finally
    FreeAndNil(Zieldaten);
  end;
end;                         

Mathias
Beiträge: 6918
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: filestream laden/speichern

Beitrag von Mathias »

Hier habe ich ein Muster, zur Benutzung von TStream, mit einer kleinen Änderung sollte es auch mit der ObjectList gehen.
Du machst zum Teil Umwandlungen mit IntToStr das kann nicht gut gehen.

Code: Alles auswählen

type
  TDatensatz = record
    ID: integer;
    Name: ansistring;
  end;
 
  TDatenArray = array of TDatensatz;
 
procedure ShowData(daten: TDatensatz);  // Nur zur Kontrolle
begin
  WriteLn('ID: ', daten.ID);
  WriteLn('Name: ', daten.Name);
end;
 
const
  filename = 'test.dat';
 
procedure TForm1.SaveClick(Sender: TObject);
var
  Daten: TDatenArray;
  Stream: TFileStream;
  i: integer;
  Len: longint;
begin
  SetLength(Daten, 4);
  Daten[0].ID := 1;
  Daten[0].Name := 'erster Name';
  Daten[1].ID := 2;
  Daten[1].Name := '';
  Daten[2].ID := 3;
  Daten[2].Name := 'dritter Name';
  Daten[3].ID := 4;
  Daten[3].Name := 'Lazarus programmieren';
  Stream := TFileStream.Create(filename, fmCreate);
  try
    Len := Length(Daten);
    Stream.Write(Len, SizeOf(Len));
    for i := 0 to Length(Daten) - 1 do begin
      Stream.Write(Daten[i].ID, SizeOf(Daten[i].ID));   // ID Speichern als Integer
      Len := Length(Daten[i].Name);
      Stream.Write(Len, SizeOf(Len));                   // Länge des Stringes speichern
      Stream.Write(Pointer(Daten[i].Name)^, Len);       // Den String selbst speichern
    end;
  finally
    Stream.Free;
  end;
end;
 
procedure TForm1.LoadClick(Sender: TObject);
var
  Daten: TDatenArray;
  Stream: TFileStream;
  i: integer;
  Len: longint;
begin
  Stream := TFileStream.Create(filename, fmOpenRead);
  try
    Stream.Read(Len, SizeOf(Len));
    SetLength(Daten, Len);
 
    for i := 0 to Len - 1 do begin
      Stream.Read(Daten[i].ID, SizeOf(Daten[i].ID));
      Stream.Read(Len, SizeOf(Len));
      SetLength(Daten[i].Name, Len);
      Stream.Read(Pointer(Daten[i].Name)^, Len);
    end;
  finally
    Stream.Free;
  end;
  for i := 0 to Length(Daten) - 1 do begin  // Ist alles richtig geladen ?
    ShowData(Daten[i]);
  end;
end;          
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

wp_xyz
Beiträge: 5153
Registriert: Fr 8. Apr 2011, 09:01

Re: filestream laden/speichern

Beitrag von wp_xyz »

Lass mal das mit "ZielDaten.Position := ..." weg. Der Positionszeiger eines Streams rückt genau um die Anzahl der Bytes vor, die gelesen/geschrieben worden sind. Du musst halt peinlich darauf achten, dass du die Daten in genau derselben Reihenfolge liest, wie du sie geschrieben hast.

Auch das "ZielDaten.Position := 0" nach dem Erzeugen des Streams ist unnötig, weil der Positionszeiger da automatisch auf 0 gesetzt wird.

Ich würde keine Strings in den Stream schreiben, weil das komplizierter ist als das Schreiben von Zahlen - du musst immer auch mitschreiben, wie lang der String ist, oder dein Programm muss wissen, wie lang der String sein kann. (Falls du das ganze als Textdatei haben willst, würde ich alle Strings in einer Stringliste speichern, die mit einem einzigen Befehl zu lesen bzw. zu schreiben ist.)

Die Schreibroutine als Binärdatei würde ich folgendermaßen machen (nicht getestet):

Code: Alles auswählen

 
  Zieldaten := TFileStream.Create('Basis.dat', fmCreate);
  try
    //Anzahl vorhandener Datensätze in Liste als 1. Wert der Datei schreiben
    Anzahl:=lBasisListe.Count;        // Die "Anzahl" ist ohne -1, mit -1 wäre "MaxIndex" klarer.
    Zieldaten.WriteWord(Anzahl);      // Falls Anzahl > 65535 sein kann, nimm WriteDWord.
    //Datensätze schreiben
    For I:=0 to Anzahl-1 do begin     // wichtig: -1, weil die Nummerierung bei 0 beginnt
      BasisEinheit := lBasisListe.Items[i] as TBasisEin;
      //Name      (ist ein String)
      Zieldaten.WriteWord(Length(Basiseinheit.Name));  // Länge des Strings vorweg
      Zieldaten.WriteBuffer(BasisEinheit.Name[1], Length(BasisEinheit.Name));  // alle Zeichen, beginnend beim 1.
      //Maximale Felder pro Runde  (Ist ein Integerwert)
      Zieldaten.WriteWord(BasisEinheit.MaxBewegung);
      //Maximale Aufklärungsreichweite  (Ist ein Integerwert)
      Zieldaten.WriteWord(BasisEinheit.MaxRecon);
    end;
  finally
    FreeAndNil(Zieldaten);
  end; 
Beim Lesen dann analog:

Code: Alles auswählen

 
  // Existiert die BasisListe schon? Wenn nicht, erzeugen
  if lBasisListe = nil then
    lBasisListe := TObjectList.Create
  else
    lBasisListe.Clear;  // evtl unnötig: bisherigen Listeninhalt entfernen
  Zieldaten := TFileStream.Create('Basis.dat', fmRead);
  try
    //Anzahl vorhandener Datensätze auslesen
    Anzahl := Zieldaten.ReadWord;
    //Datensätze lesen
    For I:=0 to Anzahl-1 do begin  
      BasisEinheit := TBasisEin.create;
      BasisEinheit := lBasisListe.Items[i] as TBasisEin;
      //Name      (ist ein String)
      // Stringlänge lesen
      c := Zieldaten.ReadWord;
      // Nur wenn ein String gespeichert ist...
      if c > 0 then begin
        // Stringlänge setzen
        SetLength(BasisEinheit.Name, c);
        // Zeichen lesen, beginnend ab dem 1. Zeichen
        Zieldaten.ReadBuffer(BasisEinheit.Name[1], c);
      end;
      //Maximale Felder pro Runde  (Ist ein Integerwert)
      BasisEinheit.maxBewegung := Zieldaten.ReadWord;
      //Maximale Aufklärungsreichweite  (Ist ein Integerwert)
      BasisEinheit.MaxRecon := Zieldaten.ReadWord;
      // lBasisListe mit Object füllen
      lBasisListe.Add(BasisEinheit);    
    end;
  finally
    FreeAndNil(Zieldaten);
  end; 

Hartkern
Beiträge: 69
Registriert: Sa 5. Dez 2015, 20:03
OS, Lazarus, FPC: Win10 IDE 1.6
CPU-Target: 64Bit
Wohnort: Leipzig

Re: filestream laden/speichern

Beitrag von Hartkern »

Vielen Dank euch beiden zu so später Stunde!

Sobald ich heut aus der Uni zurück bin werd icj mich an die Fehlerkorrektur setzen!

Antworten