Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Delphi schnell Zeilen zählen (https://www.delphipraxis.net/104973-schnell-zeilen-zaehlen.html)

akko5 13. Dez 2007 15:39


schnell Zeilen zählen
 
Hallo,
Ich bin gerade dabei, einen Code zu schreiben, der schnell die Zeilen einer Textdatei zählt.

Zuerst hatte ich das, funktioniert zwar sehr gut, ist aber für große Dateien einfach zu langsam.
Delphi-Quellcode:
function CountLines1(const sFile: String): Integer;
var
  fInput: TextFile;
begin
  Result := 0;
  AssignFile(fInput, sFile);
  try
    ReSet(fInput);
    try
      while not EOF(fInput) do
      begin
        ReadLn(fInput);
        Inc(Result);
      end;
    finally
      CloseFile(fInput);
    end;
  except
    //Result := -1;
  end;
end;
Dann hatte ich die Idee, die Datei in Blöcken auszulesen und dann die Zeilenumbrüche zu zählen.
Diese Methode arbeitet viel schneller, nur leider gibt sie immer (meistens) zuviele Zeilen zurück. :gruebel:
Delphi-Quellcode:
function PosCount(const SubStr, S: String): Integer;
var
  iPos: Integer;
begin
  Result := 0;
  iPos := 0;
  repeat
    iPos := PosEx(SubStr, S, Succ(iPos));
    if iPos > 0 then Inc(Result);
  until iPos = 0;
end;

function CountLines2(const sFile: String): Integer;
const
  BUFFER_SIZE = 8192;
var
  fInput: File;
  cBuffer: Array[1..BUFFER_SIZE] of Char;
  iRead: Integer;
begin
  Result := 0;
  AssignFile(fInput, sFile);
  try
    ReSet(fInput, 1);
    try
      while not EOF(fInput) do
      begin
        BlockRead(fInput, cBuffer, BUFFER_SIZE, iRead);
        Inc(Result, PosCount(#10, cBuffer));
      end;
    finally
      CloseFile(fInput);
    end;
  except
    //Result := -1;
  end;
end;
Wäre nett, wenn sich das mal jmd. anschauen könnte und mir sagen kann, wieso der 2. Code nicht richtig funktioniert.

Danke!

himitsu 13. Dez 2007 15:51

Re: schnell Zeilen zählen
 
Vielleicht solltest du mal nur den Teil in cBuffer auswerten, der auch von BlockRead mit Daten gefüllt wurde?

etwa so:
Code:
Inc(Result, PosCount(#10, [color=red]Copy(cBuffer, 1, iRead)[/color]));

PS: es wäre eventuell auch hilfreich, wenn du erwähnt hättes um wieviel falsch gezählt wird.


[ot] ach ja, das ist ein Reset und kein Re-Set :stupid:

Ghostwalker 13. Dez 2007 15:54

Re: schnell Zeilen zählen
 
Wenns nur um simple Textdateien geht:

Delphi-Quellcode:
function GetLineCount(AFile:String):Integer;
var
  str : Tstrings;

begin
  str := Tstringlist.create;
  str.loadfromfile(Afile);
  result := str.count;
  str.free;
end;

akko5 13. Dez 2007 15:57

Re: schnell Zeilen zählen
 
Zitat:

Zitat von himitsu
Vielleicht solltest du mal nur den Teil in cBuffer auswerten, der auch von BlockRead mit Daten gefüllt wurde?

etwa so:
Code:
Inc(Result, PosCount(#10, [color=red]Copy(cBuffer, 1, iRead)[/color]));

PS: es wäre eventuell auch hilfreich, wenn du erwähnt hättes um wieviel falsch gezählt wird.


[ot] ach ja, das ist ein Reset und kein Re-Set :stupid:

Oh, Mann vielen Dank das habe ich nicht bedacht.

Danke :spin2:

@Ghostwalker
Für kleine Dateien gut, aber für große ungeeignet.


//Für eine 120 MB Datei, braucht die 2. Funktion ~ 350 ms, wenn das nicht mal schnell ist! :-D

Christian Seehase 13. Dez 2007 16:02

Re: schnell Zeilen zählen
 
Moin Ghostwalker,

das hab' ich früher auch mal so gemacht, und wenn es sich vornehmlich um kleine Textdateien handelt, ist das wohl auch noch gangbar, aber je grösser diese werden, umso schneller wirst Du merken, dass das vollständige Laden in den Arbeitsspeicher, und anschliessende Ermitteln der Zeilen ganz böse auf die Performance drückt.
Allein schon das zeilenweise auslesen, geht da schon erheblich schneller.

KesseK 13. Dez 2007 16:22

Re: schnell Zeilen zählen
 
Zitat:

Zitat von Christian Seehase
Moin Ghostwalker,

das hab' ich früher auch mal so gemacht, und wenn es sich vornehmlich um kleine Textdateien handelt, ist das wohl auch noch gangbar, aber je grösser diese werden, umso schneller wirst Du merken, dass das vollständige Laden in den Arbeitsspeicher, und anschliessende Ermitteln der Zeilen ganz böse auf die Performance drückt.
Allein schon das zeilenweise auslesen, geht da schon erheblich schneller.

Hallo Christian, habe das gerade getestet und bin absolut nicht davon überzeugt. Auch große Dateien lassen sich damit schnell auslesen. Die Performance hielt sich auch gut.

himitsu 13. Dez 2007 16:37

Re: schnell Zeilen zählen
 
abgesehn wenn die Datei größer als der freie Speicher (RAM) ist und dieses auch speichermäßig etwas unökonomisch ist (an dem Beispiel über 120 MB welche die TStringList in den RAM lädt gegen 8 KB mit CountLines2)

und soll ich dir mal 'ne "kleine" 5 GB Textdatei leihen?


@akko5: ein größerer Speicherblock könnte nicht schaden, damit verringern sich die Lesezugriffe und das ganze ist auch etwas optimaler in Bezug auf die WindowsFileCache/Dateiverwaltung.
Delphi-Quellcode:
BUFFER_SIZE = 65536; // 64 KB

akko5 13. Dez 2007 16:58

Re: schnell Zeilen zählen
 
Zitat:

Zitat von himitsu
@akko5: ein größerer Speicherblock könnte nicht schaden, damit verringern sich die Lesezugriffe und das ganze ist auch etwas optimaler in Bezug auf die WindowsFileCache/Dateiverwaltung.
Delphi-Quellcode:
BUFFER_SIZE = 65536; // 64 KB

Ja Danke. Hab auch noch einen kleinen Fehler gefunden, dass die letzte Zeile nicht immer gezählt wurde.
So sollte die Funktion perfekt sein:
Delphi-Quellcode:
function PosCount(const SubStr, S: String): Integer;
var
  iPos: Integer;
begin
  Result := 0;
  iPos := 0;
  repeat
    iPos := PosEx(SubStr, S, Succ(iPos));
    if iPos > 0 then Inc(Result);
  until iPos = 0;
end;

function CountLines(const sFile: String): Integer;
const
  BUFFER_SIZE = 65536;
var
  fInput: File;
  cBuffer: Array[1..BUFFER_SIZE] of Char;
  iRead: Integer;
  sBuffer: String;
begin
  Result := 0;
  AssignFile(fInput, sFile);
  try
    ReSet(fInput, 1);
    try
      while not EOF(fInput) do
      begin
        BlockRead(fInput, cBuffer, BUFFER_SIZE, iRead);
        sBuffer := Copy(cBuffer, 1, iRead);
        Inc(Result, PosCount(#10, sBuffer));
      end;
      if sBuffer[Length(sBuffer)] <> #10 then Inc(Result);
    finally
      CloseFile(fInput);
    end;
  except
    //Result := -1;
  end;
end;
Für eine ~100 MB Textdatei (9105111 Zeilen) brauchte die Funktion 296 ms. :thumb:

MfG

himitsu 13. Dez 2007 17:14

Re: schnell Zeilen zählen
 
iRead, falls die Letzte Zeile nicht den Puffer ausfüllte ;)
Delphi-Quellcode:
iRead := 0;
while not EOF(fInput) do
begin
  BlockRead(fInput, cBuffer, BUFFER_SIZE, iRead);
  sBuffer := Copy(cBuffer, 1, iRead);
  Inc(Result, PosCount(#10, Copy(cBuffer, 1, iRead)));
end;
if (iRead > 0) and (sBuffer[iRead] <> #10) then Inc(Result);
direkt in den String eingelesen (PosCount/PosEx möchten das ja eh in einem String) und intern gezählt:
Delphi-Quellcode:
function CountLines2(const sFile: String): Integer;
const
  BUFFER_SIZE = 65536;
var
  fInput: File;
  S: String;
  iRead, iPos: Integer;
begin
  Result := 0;
  AssignFile(fInput, sFile);
  try
    Reset(fInput, 1);
    try
      iRead := 0;
      while not EOF(fInput) do
      begin
        SetLength(S, BUFFER_SIZE);
        BlockRead(fInput, S[1], BUFFER_SIZE, iRead);
        SetLength(S, iRead);
        iPos := 0;
        repeat
          iPos := PosEx(#0, S, Succ(iPos));
          if iPos > 0 then Inc(Result);
        until iPos = 0;
      end;
      if (iRead > 0) and (S[iRead] <> #10) then Inc(Result);
    finally
      CloseFile(fInput);
    end;
  except
    //Result := -1;
  end;
end;

Christian Seehase 13. Dez 2007 18:11

Re: schnell Zeilen zählen
 
Moin Akko,

das ist ja mal ein interessantes Problem ;-)

Probier' das hier mal aus:

Delphi-Quellcode:
function csCountLines(const AsFilepath : string) : Integer;

const
  BUFSIZE = 65536;

var
  fs    : TFileStream;
  sBuf  : array [1..BUFSIZE] of char;
  iLines : Integer;
  iRead : integer;
  i     : Integer;

begin
  fs := TFileStream.Create(AsFilepath,fmOpenRead);
  try
    iLines := 0;
    iRead := fs.Read(sBuf,BUFSIZE);
    while iRead = BUFSIZE do begin
      for i := 1 to iRead do begin
        Inc(iLines,Ord(sBuf[i]=#10));
      end;
      iRead := fs.Read(sBuf,BUFSIZE);
    end;
    for i := 1 to iRead do begin
      Inc(iLines,Ord(sBuf[i]=#10));
    end;
    if (iRead > 0) and (sBuf[iRead] <> #10) then inc(iLines);
  finally
    fs.Free;
  end;
  Result := iLines;
end;
Auf dem gleichen Rechner mit der gleichen Datei getestet, ist die noch einmal schneller als Deine ;-)
Bei den offensichtlich sehr kurzen Zeilen, die Du da hast, ist das sequenzielle Durchsuchen des Buffers offensichtlich effizienter als die Sprünge mit PosEx.

@Philipp:
Da hast Du wohl recht. Inzwischen ist die Verwendung von TStringList wohl doch schneller als das rein zeilenweise Lesen.
Zuletzt hatte ich das unter NT 4 mit einem in D2 geschriebenen Programm getestet :mrgreen:


Alle Zeitangaben in WEZ +1. Es ist jetzt 17:13 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz