StringList.SaveToFile: an bestehende Datei anhängen?

Für alles, was in den übrigen Lazarusthemen keinen Platz, aber mit Lazarus zutun hat.
Antworten
Benutzeravatar
photor
Beiträge: 444
Registriert: Mo 24. Jan 2011, 21:38
OS, Lazarus, FPC: Arch Linux: L 2.2.6 FPC 3.2.2 (Gtk2)
CPU-Target: 64Bit

StringList.SaveToFile: an bestehende Datei anhängen?

Beitrag von photor »

Hallo Forum,

Einfache Frage (und ich fürchte, ich kenne die Antwort):

Man kann ja eine StringList mit StringList.SaveToFile("Datei.txt") in eine Datei speichern. Die wird dabei leider überschrieben.

Kann man das verhindern und die den Inhalt der StringList an eine bestehende Datei anhängen? Durch einen Parameter, ein Flag oder ähnlich?

Der Huntergrund ist, dass in der StringList einige Minimum- und Maximum-Werte für die Durchläufe einer Rechnung gesammelt werden. Da das sehr viele Durchläufe (über lange Zeit) sein können, kommt es vor, dass so eine Rechnung abgebrochen werden muss, oder das auch schon mal von alleine tut. Wenn man die neu startet kann man sagen, ab welchem Durchlauf weiter gerechnet werden soll. Dabei wird leider die Datei mit dem Min- und Max-Werten überschrieben.

Der Work-around ist eigentlich klar: man liest die Datei vorher in die StringList mittels .LoadFromFile(), bevor man man neue Zeilen anhängt. Ist aber ein bisschen "durch die Brust ins Auge.

Gibt es eine einfache Möglichkeit - neben dem Würg-around? Und wenn "nein", warum eigentlich nicht?

Ciao,
Photor

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

Re: StringList.SaveToFile: an bestehende Datei anhängen?

Beitrag von theo »

Man könnte einen TFileStream zu Hülfe nehmen, wenn man zu wenig RAM hat für den Text.

Code: Alles auswählen

var
  FStream: TFileStream;
  SL:TStringList;
begin
  FStream := TFileStream.Create('/home/theo/filename.txt', fmOpenWrite);
  FStream.Seek(0, soEnd);
  SL:=TStringList.Create;
  SL.Text:='testen';
  FStream.Write(SL.Text[1],Length(SL.Text));
  SL.free;
  FStream.free;
end;     

Benutzeravatar
Jorg3000
Lazarusforum e. V.
Beiträge: 169
Registriert: So 10. Okt 2021, 10:24
OS, Lazarus, FPC: Win64
Wohnort: NRW

Re: StringList.SaveToFile: an bestehende Datei anhängen?

Beitrag von Jorg3000 »

Das Anhängen an die Datei ist eine schöne Lösung von Theo.
Der zweimalige Aufruf von SL.Text ist jedoch unschön, wenn es sich um eine Liste mit vielen Strings handelt.

Code: Alles auswählen

var
  FStream: TFileStream;
  SL:TStringList;
  s: String;
begin
  // hier bestehende SL vorausgesetzt
  s:=SL.Text;
  if s<>'' then
  	try
  		FStream :=nil;  // wegen Finally falls schon TFileStream.Create() fehlschlägt
  		FStream := TFileStream.Create('/home/theo/filename.txt', fmOpenWrite);
  		FStream.Seek(0, soEnd);
  		FStream.Write(s[1],Length(s));
  	finally
  		FStream.free;
  	end;	
end;

Socke
Lazarusforum e. V.
Beiträge: 3158
Registriert: Di 22. Jul 2008, 19:27
OS, Lazarus, FPC: Lazarus: SVN; FPC: svn; Win 10/Linux/Raspbian/openSUSE
CPU-Target: 32bit x86 armhf
Wohnort: Köln
Kontaktdaten:

Re: StringList.SaveToFile: an bestehende Datei anhängen?

Beitrag von Socke »

Wenn die zu schreibenden Daten ohnehin in einer StringList vorhanden sind, ist der Aufruf von .Text komplett überflüssig. Das braucht gerade bei großen Stringlisten zu viel Zeit und Speicher. Da kann man die einzelnen Strings doch direkt in die Datei schreiben.
Man kombiniere einfach theo's Aufruf FStream.Seek(0, soEnd) um an das Ende eines Streams zu springen mit TStrings.SaveToFile miteinander:

Code: Alles auswählen

Procedure AppendStringsToFile(aStrings: TStrings; const FileName: string);
Var TheStream : TFileStream;

begin
  TheStream:=TFileStream.Create(FileName,fmOpenWrite);
  try
    TheStream.Seek(0, soEnd); 
    aStrings.SaveToStream(TheStream);
  finally
    TheStream.Free;
  end;
end;   
MfG Socke
Ein Gedicht braucht keinen Reim//Ich pack’ hier trotzdem einen rein

Benutzeravatar
photor
Beiträge: 444
Registriert: Mo 24. Jan 2011, 21:38
OS, Lazarus, FPC: Arch Linux: L 2.2.6 FPC 3.2.2 (Gtk2)
CPU-Target: 64Bit

Re: StringList.SaveToFile: an bestehende Datei anhängen?

Beitrag von photor »

Hallo Forum,

Zunächst mal "Danke" für den ganzen Input.
theo hat geschrieben:
Fr 13. Jan 2023, 16:38
Man könnte einen TFileStream zu Hülfe nehmen, wenn man zu wenig RAM hat für den Text.
Die Idee mit einem Stream hatte ich auch schon - ist aber (auch) nicht ganz einfach in den bisherigen Programmablauf zu integrieren (das Programm ist ein über Jahrzehnte gewachsenes - und dabei von diversen Leuten aufgezogenes; will heißen; die Struktur ist nicht immer gut durchdacht). Aber ich schaue mir das Ganze mal daraufhin an.

Socke hat geschrieben:
Fr 13. Jan 2023, 19:45
Wenn die zu schreibenden Daten ohnehin in einer StringList vorhanden sind, ist der Aufruf von .Text komplett überflüssig. Das braucht gerade bei großen Stringlisten zu viel Zeit und Speicher. Da kann man die einzelnen Strings doch direkt in die Datei schreiben.
Pro Durchlauf wird eine Zeile eingetragen. Anzahl der Durchläufe: 100 ... 900. Das, denke ich, macht keine großen Probleme im Speicher (die Maschine hat jedenfalls genug davon - die macht auch FE-Berechnungen).

theo hat geschrieben:
Fr 13. Jan 2023, 16:38
Man könnte einen TFileStream zu Hülfe nehmen, wenn man zu wenig RAM hat für den Text.
Socke hat geschrieben:
Fr 13. Jan 2023, 19:45
Man kombiniere einfach theo's Aufruf FStream.Seek(0, soEnd) um an das Ende eines Streams zu springen mit TStrings.SaveToFile miteinander:
War in etwa meine spontane Idee, als mein Kollege mit dem Bug/Wunsch kam, dass bitte alle(!) Einträge im File enthalten sind - auch nach Restart. Beim Blick in den Programm-Code war dann die Idee, neue Einträge mittels SaveToFile anzuhängen - wäre das einfachste.

Wenn ich das so richtig sehe, gibt es
  1. wohl keine Option gibt, mit der StringList.SaveToFile unmittelbar zum Anhängen an ein bestehnedes File zu bewegen ist
  2. entweder mit StringList.LoadFromFile das alte File in StringList geladen und die weiteren Einträge anhängen und am Ende wieder ganz normal mit StringList.SaveToFile rausschreiben - oder
  3. die Lösung mit FileStream.Seek an das Ende des (eventuell) vorhandenen Files gehen und dort die weiteren Einträge anhängen FileStream.Write oder mit StringList.SaveToFile speichern
Ich werde mal probieren.

Ciao,
Photor

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

Re: StringList.SaveToFile: an bestehende Datei anhängen?

Beitrag von theo »

photor hat geschrieben:
Fr 13. Jan 2023, 20:13
Die Idee mit einem Stream hatte ich auch schon - ist aber (auch) nicht ganz einfach in den bisherigen Programmablauf zu integrieren
Eigentlich ein idealer Fall für einen Class Helper.

Code: Alles auswählen

{$mode objfpc}{$H+}
...
type

  { TAppendStringList }

  TAppendStringList = class helper for TStringList
    procedure AppendToFile(const FileName: string);
  end;  

...

implementation    

{ TAppendStringList }

procedure TAppendStringList.AppendToFile(const FileName: string);
var
  TheStream: TFileStream;
  Mode: word;
begin
  if FileExists(FileName) then
    Mode := fmOpenWrite
  else
    Mode := fmCreate;  
  TheStream := TFileStream.Create(FileName, Mode);
  try
    TheStream.Seek(0, soEnd);
    Self.SaveToStream(TheStream);
  finally
    TheStream.Free;
  end;
end;

...

procedure TForm1.Button1Click(Sender: TObject);
var
  SL: TStringList;
begin
  SL := TStringList.Create;
  SL.Text := 'Test';
  Sl.AppendToFile('testeappend.txt');
  SL.Free;
end;  

Benutzeravatar
photor
Beiträge: 444
Registriert: Mo 24. Jan 2011, 21:38
OS, Lazarus, FPC: Arch Linux: L 2.2.6 FPC 3.2.2 (Gtk2)
CPU-Target: 64Bit

Re: StringList.SaveToFile: an bestehende Datei anhängen?

Beitrag von photor »

Moin,

oh. Das ist ein neuer Aspekt - nicht nur hier, sondern auch für mich!

Das muss ich mir mal in Ruhe ansehen.

Merci,
Photor

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

Re: StringList.SaveToFile: an bestehende Datei anhängen?

Beitrag von theo »

photor hat geschrieben:
Sa 14. Jan 2023, 09:24
Das muss ich mir mal in Ruhe ansehen.
Schwierig ist es nicht zu verstehen.
Du erweiterst so die Funktionalität der Stringlist, ohne dafür eine Klasse ableiten zu müssen.
Falls du die Erweiterung in mehreren Units brauchst, dann lege den Class Helper z.B. in eine Unit mit Hilfsfunktionen, die du überall einbinden kannst.
Eigentlich genau was du wolltest.

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

Re: StringList.SaveToFile: an bestehende Datei anhängen?

Beitrag von wp_xyz »

theo hat geschrieben:
Fr 13. Jan 2023, 21:56
Eigentlich ein idealer Fall für einen Class Helper.
Ich versuche diese Dinger weitgehend zu vermeiden, vor allem weil ich nicht up-to-date bin. Vor einiger Zeit hat mir jemand einmal gesagt, dass es zu einem Typ nur einen Helper geben kann. Das heißt womöglich, dass ich einen existierenden Helper deaktiviere, wenn ich selbst einen Helper schreibe. Wenn also irgendwo in der FCL, RTL, LCL ein Helper für TStringList implementiert worden ist, und ich mir dafür einen eigenen Helper schreibe, gehen vielleicht Teile der FCL/RTL/LCL nicht mehr? Ist das so richtig? Oder kann ich nur den ursprünglichen Helper, den ich überschrieben habe, nicht mehr verwenden?

Ohne einen Helper, und ohne diese Unsicherheit, tut's eine einfache Prozedur auch, ist natürlich nicht so schick:

Code: Alles auswählen

procedure AppendStringsToFile(AStrings: TStrings; AFileName: String);
var
  stream: TFileStream;
  Mode: word;
begin
  if FileExists(AFileName) then
    Mode := fmOpenWrite
  else
    Mode := fmCreate;  
  stream := TFileStream.Create(AFileName, Mode);
  try
    stream.Seek(0, soEnd);
    AStrings.SaveToStream(stream);
  finally
    stream.Free;
  end;
end;
Was der Helper (bzw. die Prozedur) evtl noch prüfen sollte, ist, ob die ursprüngliche Datei mit einem Zeilenumbruch endet, sonst wird die erste Zeile der neuen Strings direkt an die letzte Zeile der Datei angehängt.

Socke
Lazarusforum e. V.
Beiträge: 3158
Registriert: Di 22. Jul 2008, 19:27
OS, Lazarus, FPC: Lazarus: SVN; FPC: svn; Win 10/Linux/Raspbian/openSUSE
CPU-Target: 32bit x86 armhf
Wohnort: Köln
Kontaktdaten:

Re: StringList.SaveToFile: an bestehende Datei anhängen?

Beitrag von Socke »

wp_xyz hat geschrieben:
Sa 14. Jan 2023, 12:35
Vor einiger Zeit hat mir jemand einmal gesagt, dass es zu einem Typ nur einen Helper geben kann. Das heißt womöglich, dass ich einen existierenden Helper deaktiviere, wenn ich selbst einen Helper schreibe. Wenn also irgendwo in der FCL, RTL, LCL ein Helper für TStringList implementiert worden ist, und ich mir dafür einen eigenen Helper schreibe, gehen vielleicht Teile der FCL/RTL/LCL nicht mehr? Ist das so richtig? Oder kann ich nur den ursprünglichen Helper, den ich überschrieben habe, nicht mehr verwenden?
Dein Code kann immer nur einen Helper pro Typ nutzen. Wenn die RTL/FCL einen Helper für TStrings oder TStringList definiert, kannst du deinen Helper davon ableiten (wie bei ganz normalen Klassen) und hast dann auch dessen Methoden.
Wenn du das nicht machst, überdeckst du nur den Standard-Helper und nur die Methoden deines eigenen Helpers können aufgerufen werden.

Da die RTL/FCL/LCL deine Units nicht kennt, wird sie immer mit den dort definierten Helpern arbeiten können. Das ist genau so wie bei allen anderen Funktionen/Methoden/Klassen und so weiter - es wird nur das beachtet, was im Uses-Abschnitt steht.
MfG Socke
Ein Gedicht braucht keinen Reim//Ich pack’ hier trotzdem einen rein

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

Re: StringList.SaveToFile: an bestehende Datei anhängen?

Beitrag von wp_xyz »

Socke hat geschrieben:
Sa 14. Jan 2023, 12:56
wp_xyz hat geschrieben:
Sa 14. Jan 2023, 12:35
Vor einiger Zeit hat mir jemand einmal gesagt, dass es zu einem Typ nur einen Helper geben kann. Das heißt womöglich, dass ich einen existierenden Helper deaktiviere, wenn ich selbst einen Helper schreibe. Wenn also irgendwo in der FCL, RTL, LCL ein Helper für TStringList implementiert worden ist, und ich mir dafür einen eigenen Helper schreibe, gehen vielleicht Teile der FCL/RTL/LCL nicht mehr? Ist das so richtig? Oder kann ich nur den ursprünglichen Helper, den ich überschrieben habe, nicht mehr verwenden?
Dein Code kann immer nur einen Helper pro Typ nutzen. Wenn die RTL/FCL einen Helper für TStrings oder TStringList definiert, kannst du deinen Helper davon ableiten (wie bei ganz normalen Klassen) und hast dann auch dessen Methoden.
Wenn du das nicht machst, überdeckst du nur den Standard-Helper und nur die Methoden deines eigenen Helpers können aufgerufen werden.

Da die RTL/FCL/LCL deine Units nicht kennt, wird sie immer mit den dort definierten Helpern arbeiten können. Das ist genau so wie bei allen anderen Funktionen/Methoden/Klassen und so weiter - es wird nur das beachtet, was im Uses-Abschnitt steht.
OK, verstanden. Anders ausgedrückt: Es kann sehr wohl verschiedene Helper pro Typ geben, sofern diese in unterschiedlichen Units stehen. Und es wird immer derjenige Helper verwendet, dessen Unit in der "uses"-Zeile der ihn aufrufenden Unit genannt ist. Oder?

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

Re: StringList.SaveToFile: an bestehende Datei anhängen?

Beitrag von theo »

wp_xyz hat geschrieben:
Sa 14. Jan 2023, 12:35
Ich versuche diese Dinger weitgehend zu vermeiden....
Naja, wenn du die anderen Helper (Ggf.) nicht benötigst, sehe ich da keine Gefahr.
Die RTL/FCL/LCL wirst du damit nicht aus der Ruhe bringen.
wp_xyz hat geschrieben:
Sa 14. Jan 2023, 14:25
OK, verstanden. Anders ausgedrückt: Es kann sehr wohl verschiedene Helper pro Typ geben, sofern diese in unterschiedlichen Units stehen. Und es wird immer derjenige Helper verwendet, dessen Unit in der "uses"-Zeile der ihn aufrufenden Unit genannt ist. Oder?
Ja, deshalb hatte ich auch geschrieben: "Falls du die Erweiterung in mehreren Units brauchst, dann lege den Class Helper z.B. in eine Unit mit Hilfsfunktionen, die du überall einbinden kannst."

Ich finde das Schick. Bei nicht-visuellen Komponenten ginge ein abgeleitete Klasse nat. auch recht einfach.
Trotzdem müsste er so Deklarationen ändern, was er mit dem Helper nicht muss.

Antworten