letzte Zeilen einer riesengroßen Textdatei in ein Memofeld

Für Fragen zur Programmiersprache auf welcher Lazarus aufbaut
Benutzeravatar
willi4willi
Lazarusforum e. V.
Beiträge: 176
Registriert: Sa 1. Nov 2008, 18:06
OS, Lazarus, FPC: Lazarus 3.8 FPC 3.2.2 x86_64-win64-win32/win64 x86_64-linux-gtk2
CPU-Target: i386, win64, arm

letzte Zeilen einer riesengroßen Textdatei in ein Memofeld

Beitrag von willi4willi »

Hallo allerseits,

folgendes Problem:

Ein Logfile ist riesengroß geworden (ca. 20 MB). Die letzten 10 Zeilen sollen periodisch (z.B. mittels Timer) in einem Memo-Feld dargestellt werden.

Mit AssignFile(TextFile,Dateiname) und Seek(TextFile,Position) kann ich nicht in der Datei nach hinten springen.
Und mit TStringList.LoadFromFile(Dateiname) wird die gesamte Datei eingelesen. Bei der Dateigröße ist das der Horror.

Hat jemand eine Idee, wie ich zur zehntletzten Zeile springen kann und ab dort bis zum Dateiende die letzten zehn Zeilen einlesen kann, ohne durch die Danze Datei zu müssen?

Viele Grüße

Willi4Willi
 

Viele Grüße

Willi4Willi

------------

Euklid
Lazarusforum e. V.
Beiträge: 2808
Registriert: Fr 22. Sep 2006, 10:38
OS, Lazarus, FPC: Lazarus v2.0.10, FPC 3.2.0
Wohnort: Hessen
Kontaktdaten:

Re: letzte Zeilen einer riesengroßen Textdatei in ein Memofeld

Beitrag von Euklid »

Bei Textdateien hat man denke ich keine andere Wahl, als die Datei vollständig einzulesen.
(wobei READLN schneller sein könnte als stringlist)

http://www.lazarusforum.de/portal.php?c ... ery=readln" onclick="window.open(this.href);return false;

Linkat
Lazarusforum e. V.
Beiträge: 582
Registriert: So 10. Sep 2006, 23:24
OS, Lazarus, FPC: Linux Mint 22.1; Lazarus 4.2 FPC 3.2.2; RaspiOS
CPU-Target: AMD 64, ARM 64
Wohnort: nr Stuttgart

Re: letzte Zeilen einer riesengroßen Textdatei in ein Memofeld

Beitrag von Linkat »

Hallo,
ich würde dieLogdatei in eine Stringliste einlesen und mit:

Code: Alles auswählen

for i:=stringliste.count-11 to stringlist.count-1 do begin
   ....
end;
die 10 Zeilen darstellen. Ob readln schneller ist, kann ich nicht sagen, aber Ich arbeite auch sehr häufig mit großen Logfiles. Die Geschwindigkeit war noch nie ein Problem.

Gruß, Linkat
Linux Mint 22.1; Lazarus 4.2 FPC 3.2.2; RaspiOS

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

Re: letzte Zeilen einer riesengroßen Textdatei in ein Memofeld

Beitrag von theo »

Wenn man abschätzen kann, wie gross eine Zeile werden kann, könnte man evtl. auch "genügend" Bytes vom Ende mittels TFileStream einlesen, und dann in einer StringList weiterverarbeiten.

soFromEnd:
http://www.freepascal.org/docs-html/rtl ... .seek.html" onclick="window.open(this.href);return false;

Ist nur so eine Idee, die Performance kann ich nicht einschätzen.

Scotty
Beiträge: 768
Registriert: Mo 4. Mai 2009, 13:24
OS, Lazarus, FPC: Arch Linux, Lazarus 1.3 r44426M FPC 2.6.4
CPU-Target: x86_64-linux-qt/gtk2
Kontaktdaten:

Re: letzte Zeilen einer riesengroßen Textdatei in ein Memofeld

Beitrag von Scotty »

Wie wäre es mit einem Aufruf von tail?

Benutzeravatar
willi4willi
Lazarusforum e. V.
Beiträge: 176
Registriert: Sa 1. Nov 2008, 18:06
OS, Lazarus, FPC: Lazarus 3.8 FPC 3.2.2 x86_64-win64-win32/win64 x86_64-linux-gtk2
CPU-Target: i386, win64, arm

Re: letzte Zeilen einer riesengroßen Textdatei in ein Memofeld

Beitrag von willi4willi »

Danke für die Tipps!

Das Einlesen in eine StringList ist zu langsam (Dateigröße 24 MB und über 68000 Zeilen).

Mit Readln stelle ich nun die Größe der Datei fest, wandere dann bis zu 10 Zeilen vor das Ende und lese dann die restlichen 10 Zeilen ein.

Code: Alles auswählen

procedure TForm1.LadeMitReadln;
var LogFile : TextFile;
     S : String;
     Lauf, Zeilen : longint;
Begin
  if FileExists(OpenDialog1.FileName) then
  begin
   AssignFile(LogFile,OpenDialog1.FileName);
   FileMode := fmOpenRead;
   Reset(LogFile);
   memo1.Lines.BeginUpdate;
   memo1.Lines.Clear;
   Zeilen:=0;
   while not eof(LogFile) do
   begin
     Readln(LogFile);
     inc(Zeilen);
   end;
   CloseFile(LogFile);
   reset(LogFile);
   for Lauf:=0 to Zeilen-10 do Readln(LogFile);
   while not eof(LogFile) do
   begin
     Readln(LogFile,s);
     Memo1.Lines.Add(s);
   end;
   Memo1.Lines.EndUpdate;
   CloseFile(LogFile);
  end;
end;

Das ist zwar ziemlich umständlich, aber scheinbar die beste Lösung. Wenn ich den Timer auf 1000 ms einstelle dann ist das auf meinem Rechner noch akzeptabel.

Klar, das Unix-Kommando tail oder less machen genau das, wass ich will, aber unter Windows?

Danke allen und ein schönes Wochenende!
 

Viele Grüße

Willi4Willi

------------

Euklid
Lazarusforum e. V.
Beiträge: 2808
Registriert: Fr 22. Sep 2006, 10:38
OS, Lazarus, FPC: Lazarus v2.0.10, FPC 3.2.0
Wohnort: Hessen
Kontaktdaten:

Re: letzte Zeilen einer riesengroßen Textdatei in ein Memofeld

Beitrag von Euklid »

Willi4Willi: Noch deutlich schneller als Readln dürfte Blockread sein. Damit liesst Du richtig grosse Blöcke auf einen Schlag ein und kannst dann den letzten Block untersuchen. Das dürfte aber noch aufwändiger sein...^^

http://www.lazarusforum.de/portal.php?c ... =blockread" onclick="window.open(this.href);return false;

Benutzeravatar
m.fuchs
Lazarusforum e. V.
Beiträge: 2897
Registriert: Fr 22. Sep 2006, 19:32
OS, Lazarus, FPC: Winux (Lazarus 2.0.10, FPC 3.2.0)
CPU-Target: x86, x64, arm
Wohnort: Berlin
Kontaktdaten:

Re: letzte Zeilen einer riesengroßen Textdatei in ein Memofeld

Beitrag von m.fuchs »

willi4willi hat geschrieben:Klar, das Unix-Kommando tail oder less machen genau das, wass ich will, aber unter Windows?
Es gibt auch Tail für Windows, zum Beispiel: http://tailforwin32.sourceforge.net/. Hat auch gleich noch nette Features wie Highlighting von bestimmten Begriffen und ähnliches.
0118999881999119725-3

Software, Bibliotheken, Vorträge und mehr: https://www.ypa-software.de

Benutzeravatar
corpsman
Lazarusforum e. V.
Beiträge: 1781
Registriert: Sa 28. Feb 2009, 08:54
OS, Lazarus, FPC: Linux Mint Mate, Lazarus GIT Head, FPC 3.0
CPU-Target: 64Bit
Wohnort: Stuttgart
Kontaktdaten:

Re: letzte Zeilen einer riesengroßen Textdatei in ein Memofeld

Beitrag von corpsman »

Ohh, wenn ich das sehe, 2-Pass wo es einer tun würde ...

Code: Alles auswählen

procedure TForm1.LadeMitReadln;
var LogFile : TextFile;
     S : String;
     Lauf, Zeilen : longint;
Begin
  if FileExists(OpenDialog1.FileName) then
  begin
   AssignFile(LogFile,OpenDialog1.FileName);
   FileMode := fmOpenRead;
   Reset(LogFile);
   memo1.Lines.BeginUpdate;
   memo1.Lines.Clear;
   Zeilen:=0;
   while not eof(LogFile) do
   begin
     Readln(LogFile);
     inc(Zeilen);
   end;
   CloseFile(LogFile);
   reset(LogFile);
   for Lauf:=0 to Zeilen-10 do Readln(LogFile);
   while not eof(LogFile) do
   begin
     Readln(LogFile,s);
     Memo1.Lines.Add(s);
   end;
   Memo1.Lines.EndUpdate;
   CloseFile(LogFile);
  end;
end;
wie wärs mit sowas :

Code: Alles auswählen

procedure TForm1.LadeMitReadln;
var
  LogFile: TextFile;
  buffer: array[0..9] of string;
  buffer_ptr: Integer;
  i: integer;
begin
  if FileExists(OpenDialog1.FileName) then begin
    AssignFile(LogFile, OpenDialog1.FileName);
    Reset(LogFile);
    buffer_ptr := 0;
    while not eof(LogFile) do begin
      buffer[buffer_ptr] := Readln(LogFile);
      buffer_ptr := (buffer_ptr + 1) mod 10;
    end;
    CloseFile(LogFile);
    memo1.Lines.BeginUpdate;
    memo1.Lines.Clear;
    for i := 0 to 9 do begin
      Memo1.Lines.Add(buffer[(buffer_ptr - i + 10) mod 10]);
    end;
    Memo1.Lines.EndUpdate;
  end;
end;
--
Just try it

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

Re: letzte Zeilen einer riesengroßen Textdatei in ein Memofeld

Beitrag von theo »

Willst du meinen Vorschlag nicht wenigstens testen?

Das geht doch recht schnell:

Code: Alles auswählen

function ReadTail(FileName:AnsiString; Lines:integer=10; MaxLineSize:integer=80):AnsiString;
var fs:TFileStream;
  sl:TStringList;
  Bts:integer;
  Buf:AnsiString;
begin
  fs:=TFileStream.Create(FileName,fmOpenRead);
  bts:=Lines*MaxLineSize;
  fs.Seek(-bts,soFromEnd);
  SetLength(Buf,bts);
  fs.Read(Buf[1],bts);
  sl:=TStringList.Create;
  sl.Text:=Buf;
  sl.BeginUpdate;
  Repeat
    sl.Delete(0);
  until sl.Count<Lines+1;
  sl.EndUpdate;
  Result:=sl.text;
  sl.free;
  fs.free;
end;
Das kann man noch sicherer und evtl. besser machen, aber müsste klappen.

Benutzeravatar
willi4willi
Lazarusforum e. V.
Beiträge: 176
Registriert: Sa 1. Nov 2008, 18:06
OS, Lazarus, FPC: Lazarus 3.8 FPC 3.2.2 x86_64-win64-win32/win64 x86_64-linux-gtk2
CPU-Target: i386, win64, arm

Re: letzte Zeilen einer riesengroßen Textdatei in ein Memofeld

Beitrag von willi4willi »

Hallo,

Vielen Dank für die rege Teilnahme an dieser Problemstellung.

Die Idee von corpsmann ist schon gar nicht mal so schlecht. Ich hatte mit meiner Lösung ca. 610 ms benötigt. Durch den einen Durchlauf komme ich auf ca. 330 ms.

So funktionierte es:

Code: Alles auswählen

procedure TForm1.LadeMitReadln;
var
  LogFile: TextFile;
  buffer: array[0..9] of string;
  buffer_ptr: Integer;
  i: integer;
begin
  if FileExists(OpenDialog1.FileName) then begin
    AssignFile(LogFile, OpenDialog1.FileName);
    Reset(LogFile);
    buffer_ptr := 0;
    while not eof(LogFile) do begin
      Readln(LogFile,buffer[buffer_ptr]);
      buffer_ptr :=  (buffer_ptr + 1) mod 10;
    end;
    CloseFile(LogFile);
    memo1.Lines.BeginUpdate;
    memo1.Lines.Clear;
    for i := 0 to 9 do
    begin
        Memo1.Lines.Add(buffer[((buffer_ptr + i) mod 10)]);
    end;
    Memo1.Lines.EndUpdate;
  end;
end;
Aber die Lösung von Teo ist total genial!

Besonders von deren Geschwindigkeit bin ich absolut begeistert. Genau der Sprung bis fast ans Ende der Datei war der springende Punkt.

Die Geschwindigkeit mit dieser Funktion lag unter 1 ms. Super!!

Alle anderen Ideen vergessen wir mal. Das ist die Lösung.

Vielen Dank an alle, die mitgedacht haben.

Willi4willi
 

Viele Grüße

Willi4Willi

------------

mschnell
Beiträge: 3444
Registriert: Mo 11. Sep 2006, 10:24
OS, Lazarus, FPC: svn (Window32, Linux x64, Linux ARM (QNAP) (cross+nativ)
CPU-Target: X32 / X64 / ARMv5
Wohnort: Krefeld

Re: letzte Zeilen einer riesengroßen Textdatei in ein Memofeld

Beitrag von mschnell »

SetLength(Buf,bts);
...
sl.Text:=Buf;

Damit belegst Du zweimal das Memory für die komplette "riesengroße Textdate" ?!?!?!?!?!

Dann schon besser:

sl.LoadFromStream(fs);

-Michael

mschnell
Beiträge: 3444
Registriert: Mo 11. Sep 2006, 10:24
OS, Lazarus, FPC: svn (Window32, Linux x64, Linux ARM (QNAP) (cross+nativ)
CPU-Target: X32 / X64 / ARMv5
Wohnort: Krefeld

Re: letzte Zeilen einer riesengroßen Textdatei in ein Memofeld

Beitrag von mschnell »

willi4willi hat geschrieben:Die Geschwindigkeit mit dieser Funktion lag unter 1 ms. Super!!
Dann war die Datei wohl doch nicht so riesig :D


-Michael

Benutzeravatar
willi4willi
Lazarusforum e. V.
Beiträge: 176
Registriert: Sa 1. Nov 2008, 18:06
OS, Lazarus, FPC: Lazarus 3.8 FPC 3.2.2 x86_64-win64-win32/win64 x86_64-linux-gtk2
CPU-Target: i386, win64, arm

Re: letzte Zeilen einer riesengroßen Textdatei in ein Memofeld

Beitrag von willi4willi »

Hallo Michael,

vielleicht habe ich einen Denkfehler, aber wenn ich mit

sl.LoadFromStream(fs);

arbeite, wird dann nicht die ganze Datei eingelesen? Bei der Lösung von Theo sind es nur die letzten 10 Zeilen.

Geht das sl.LoadFromStream(fs) schneller als sl.LoadFromFile()? Dafür brauchte mein Programm 5400 ms.

Die Text-Datei hat 25.806.431 Byte. Nö, das ist nicht groß - verglichen mit einem FullHD-Video. ;-)

Viele Grüße

Willi4Willi
 

Viele Grüße

Willi4Willi

------------

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

Re: letzte Zeilen einer riesengroßen Textdatei in ein Memofeld

Beitrag von theo »

mschnell hat geschrieben:SetLength(Buf,bts);
...
sl.Text:=Buf;

Damit belegst Du zweimal das Memory für die komplette "riesengroße Textdate" ?!?!?!?!?!

-Michael
???? Kopfschüttel.

Antworten