Delphi-PRAXiS
Seite 3 von 4     123 4      

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Sonstige Fragen zu Delphi (https://www.delphipraxis.net/19-sonstige-fragen-zu-delphi/)
-   -   32 Bit, TStringList, Textdatei mit 30Mio. Zeilen (https://www.delphipraxis.net/186612-32-bit-tstringlist-textdatei-mit-30mio-zeilen.html)

hoika 24. Sep 2015 14:10

AW: 32 Bit, TStringList, Textdatei mit 30Mio. Zeilen
 
Hallo,
nix gegen den, der das damals so programmiert hat ... (ich war es nicht).

Hier meine Unit.


Heiko


Delphi-Quellcode:
unit LargeStringList;

interface

uses
  Classes;

type
  TLargeStringList = class(TStrings)
    private
      FItems: array of String;

      function GetField(Index: Integer): String;
    public
      property Strings[Index: Integer]: String read GetField; default;

      constructor Create;
      destructor Destroy; override;
      function Count: Integer;
      function Add(const S: String): Integer; override;
      procedure Clear; override;
      procedure LoadFromFile(const DateiName: String); override;
      procedure SaveToFile(const DateiName: String); override;
      procedure AppendToFile(const DateiName: String);
      function Text: String;
      function TextKurz: String;
      function Get(Index: Integer): string; override;
      function GetCount: Integer; override;
      procedure Delete(Index: Integer); override;
      procedure Insert(Index: Integer; const S: String); override;
  end;

implementation

uses
  SysUtils;

{ TLargeStringList }

function TLargeStringList.Add(const S: String): Integer;
var
  iLaenge: Integer;
begin
  iLaenge := Length(FItems);
  SetLength(FItems, iLaenge+1);
  FItems[iLaenge] := S;

  Result := iLaenge;
end;

procedure TLargeStringList.Clear;
begin
  FItems := nil;
end;

function TLargeStringList.Count: Integer;
begin
  Result := Length(FItems);
end;

constructor TLargeStringList.Create;
begin
  inherited ;

  FItems := nil;
end;

destructor TLargeStringList.Destroy;
begin
  FItems := nil;

  inherited;
end;

function TLargeStringList.Get(Index: Integer): string;
begin
  Result := FItems[Index];
end;

function TLargeStringList.GetCount: Integer;
begin
  Result := Length(FItems);
end;

procedure TLargeStringList.Delete(Index: Integer);
begin
  raise Exception.Create('nicht implementiert');
end;

procedure TLargeStringList.Insert(Index: Integer; const S: String);
begin
  raise Exception.Create('nicht implementiert');
end;

function TLargeStringList.GetField(Index: Integer): String;
begin
  Result := FItems[Index];
end;

procedure TLargeStringList.LoadFromFile(const DateiName: String);
var
  TextDatei: TextFile;
  Puffer: array[0..16384] of AnsiChar;
  sZeile: String;
begin
  FItems := nil;

  System.SetTextBuf(TextDatei, Puffer);
  AssignFile(TextDatei, DateiName);
  Reset(TextDatei);
  try
    while not Eof(TextDatei) do
    begin
      ReadLn(TextDatei, sZeile);
      Add(sZeile);
    end;
  finally
    CloseFile(TextDatei);
  end;
end;

procedure TLargeStringList.SaveToFile(const DateiName: String);
var
  TextDatei: TextFile;
  Puffer: array[0..16384] of AnsiChar;
  iZeile: Integer;
  sZeile: String;
begin
  System.SetTextBuf(TextDatei, Puffer);
  AssignFile(TextDatei, DateiName);
  Rewrite(TextDatei);
  try
    for iZeile := 0 to Count-1 do
    begin
      sZeile := Strings[iZeile];
      WriteLn(TextDatei, sZeile);
    end;
  finally
    CloseFile(TextDatei);
  end;
end;

procedure TLargeStringList.AppendToFile(const DateiName: String);
var
  TextDatei: TextFile;
  Puffer: array[0..16384] of AnsiChar;
  iZeile: Integer;
  sZeile: String;
begin
  if FileExists(DateiName) then
  begin
    System.SetTextBuf(TextDatei, Puffer);
    AssignFile(TextDatei, DateiName);
    System.Append(TextDatei);
    try
      for iZeile := 0 to Count-1 do
      begin
        sZeile := Strings[iZeile];
        WriteLn(TextDatei, sZeile);
      end;
    finally
      CloseFile(TextDatei);
    end;
  end
  else
  begin
    SaveToFile(DateiName);
  end;
end;

function TLargeStringList.Text: String;
var
  iZeile: Integer;
  S: String;
begin
  S := '';

  for iZeile := Low(FItems) to High(FItems) do
  begin
    S:= S+#13#10+FItems[iZeile];
  end;

  Result := S;
end;

function TLargeStringList.TextKurz: String;
var
  iZeile: Integer;
  S: String;
begin
  S := '';

  for iZeile := Low(FItems) to High(FItems) do
  begin
    S:= S+#13#10+FItems[iZeile];

    if iZeile>1000 then
    begin
      break;
    end;
  end;

  Result := S;
end;

end.

Uwe Raabe 24. Sep 2015 14:53

AW: 32 Bit, TStringList, Textdatei mit 30Mio. Zeilen
 
Das hättest du aber einfacher haben können. Deine Implementierung ist weitestgehend identisch mit TStringList, lediglich das LoadFromFile und SaveToFile gehen etwas sparsamer mit dem Speicher um. In der bordeigenen StringList wird die Datei zunächst in einen lokalen Buffer gelesen und dann erst in die StringList übertragen. Das kann bei einer sehr großen Datei in 32-Bit schon mal zu einem Out-Of-Memory führen. Die folgende Implementierung geht da etwas sparsamer mit dem Speicher um.

Es bleibt aber immer noch das Problem, daß alle Zeilen überhaupt in den Speicher passen müssen. Bedenkt man, daß intern mit 2 Byte pro Zeichen zu rechnen ist, die Datei aber womöglich in ANSI oder UTF-8 codiert ist, kann das schon bei einer ca. 1 GB großen Datei zum Problem werden. Bei 30 Millionen Zeilen bleiben da pro Zeile auch nur ca. 30 Zeichen. Ab da sollte man spätestens über eine virtuelle TStrings-Implementation nachdenken.

Delphi-Quellcode:
type
  TMemorySparingStringList = class(TStringList)
  public
    procedure LoadFromStream(Stream: TStream; Encoding: TEncoding); override;
    procedure SaveToStream(Stream: TStream; Encoding: TEncoding); override;
    procedure AppendToFile(const DateiName: String);
  end;

procedure TMemorySparingStringList.AppendToFile(const DateiName: String);
var
  writer: TStreamWriter;
  I: Integer;
begin
  writer := TStreamWriter.Create(DateiName, true);
  try
    for I := 0 to Count - 1 do begin
      writer.WriteLine(Strings[I]);
    end;
  finally
    writer.Free;
  end;
end;

procedure TMemorySparingStringList.LoadFromStream(Stream: TStream; Encoding: TEncoding);
var
  reader: TStreamReader;
begin
  BeginUpdate;
  try
    reader := TStreamReader.Create(Stream, Encoding);
    try
      while not reader.EndOfStream do begin
        Add(reader.ReadLine);
      end;
    finally
      reader.Free;
    end;
  finally
    EndUpdate;
  end;
end;

procedure TMemorySparingStringList.SaveToStream(Stream: TStream; Encoding: TEncoding);
var
  writer: TStreamWriter;
  I: Integer;
begin
  writer := TStreamWriter.Create(Stream, Encoding);
  try
    for I := 0 to Count - 1 do begin
      writer.WriteLine(Strings[I]);
    end;
  finally
    writer.Free;
  end;
end;

samso 24. Sep 2015 16:02

AW: 32 Bit, TStringList, Textdatei mit 30Mio. Zeilen
 
Zitat:

Zitat von hoika (Beitrag 1316026)
Ah ja, leider vergass ich, dass es sich hier (noch) um D2007 handelt, also keine Generics.

Und deshalb auch kein TStreamReader/TStreamWriter/TEncoding.

hoika 24. Sep 2015 16:02

AW: 32 Bit, TStringList, Textdatei mit 30Mio. Zeilen
 
Hallo,
also, ein Stream bringt mir auch sofort den Fehler.

Dass der Algorithmus mal geändert werden muss, ist klar,
aber wir haben hier genug andere Sachen zu tun.
Ich werde eh irgendwann auf 64-Bit umsteigen,
dann ist meine TLargeStringList "open end".

Danke


Heiko

Union 24. Sep 2015 16:21

AW: 32 Bit, TStringList, Textdatei mit 30Mio. Zeilen
 
Vielleicht könntest Du das Ganze virtualisieren. Die Leseroutine merkt sich nur Anfangsposition und Länge jeder Zeile der Textdatei. Das eigentliche Einlesen der Textzeilen erfolgt dann bei Zugriff. Falls Zeilen mehrfach verarbeitet werden müssen, könnten deren Texte zur Performanceverbesserung in einem Ringspeicher gecached werden.

Sir Rufo 24. Sep 2015 16:24

AW: 32 Bit, TStringList, Textdatei mit 30Mio. Zeilen
 
Zitat:

Zitat von Union (Beitrag 1316884)
Vielleicht könntest Du das Ganze virtualisieren. Die Leseroutine merkt sich nur Anfangsposition und Länge jeder Zeile der Textdatei. Das eigentliche Einlesen der Textzeilen erfolgt dann bei Zugriff. Falls Zeilen mehrfach verarbeitet werden müssen, könnten deren Texte zur Performanceverbesserung in einem Ringspeicher gecached werden.

Kann er nicht wirklich, es sei denn er baut sich auch noch einen virtuellen String-Typen, denn
Zitat:

Zitat von hoika (Beitrag 1316026)
Hallo,
an einer Stelle wird leider
Delphi-Quellcode:
StringList.Text
verwendet -> Puff.


Union 24. Sep 2015 16:34

AW: 32 Bit, TStringList, Textdatei mit 30Mio. Zeilen
 
Zitat:

Zitat von Sir Rufo (Beitrag 1316885)
Kann er nicht wirklich, es sei denn er baut sich auch noch einen virtuellen String-Typen, denn
Zitat:

Zitat von hoika (Beitrag 1316026)
Hallo,
an einer Stelle wird leider
Delphi-Quellcode:
StringList.Text
verwendet -> Puff.


Kann er doch. Da ja nur die Zeilenadressen sowie ein ggf. mehr oder weniger kleiner Cache im Speicher liegen, spart er auf jeden Fall den Speicher für das Stringarray mit den Texten. Klar macht es "Puff". Aber vermutlich viel später.

Zumal ich mir die Frage stelle, welchen sinvollen Einsatzzweck der Abruf des gesamten strings haben sollte.

Weitere Möglichkeit: Eine Stringlist, die nicht mit einem Array als Backend arbeitet, sondern mit einer doppelt verketteten Liste. Die verbraucht zwar letztendlich insgesamt etwas mehr Speicher - aber kommt mit Fragmentierung zurecht.

Uwe Raabe 24. Sep 2015 16:41

AW: 32 Bit, TStringList, Textdatei mit 30Mio. Zeilen
 
Zitat:

Zitat von Union (Beitrag 1316889)
Weitere Möglichkeit: Eine Stringlist, die nicht mit einem Array als Backend arbeitet, sondern mit einer doppelt verketteten Liste. Die verbraucht zwar letztendlich insgesamt etwas mehr Speicher - aber kommt mit Fragmentierung zurecht.

Das Array speichert aber auch nur Pointer auf die Strings. Der Speicher für alle Strings muss also auch nicht in einem Stück vorliegen.

Union 24. Sep 2015 16:58

AW: 32 Bit, TStringList, Textdatei mit 30Mio. Zeilen
 
Zitat:

Zitat von Uwe Raabe (Beitrag 1316892)
Das Array speichert aber auch nur Pointer auf die Strings. Der Speicher für alle Strings muss also auch nicht in einem Stück vorliegen.

Dennoch ist 30 M × SizeOf(Pointer) am Stück oft schwieriger vom Memory Manager aufzutreiben als die jeweils nächsten 3 × SizeOf(Pointer).

hoika 24. Sep 2015 17:16

AW: 32 Bit, TStringList, Textdatei mit 30Mio. Zeilen
 
Hallo,
übrigens bekomme ich die Datei unter XE4 64-Bit auch nicht per StringList.LoadFromFile auf (*wunder*)

Über dem Umweg per AssignFile(TextFile) und TStringList konnte ich immerhin 19 Mio Einträge erzeugen,
ohne "out of memory", 30 habe ich aber.

Nachdem jetzt meine TLargeStringList benutze, bekomme ich die Datei wenigstens auf ...

Und nochmal: Es ging mir erstmal darum, den Algorithmus möglichst nicht zu ändern.

Das .Text wird übrigens für ein Pos benutzt,
das könnte ich zur Not natürlich auch als Schleife machen,
fällt mir gerade auf, dann geht der Verbrauch nicht so hoch.
Gleich mal bauen ...


Heiko


Alle Zeitangaben in WEZ +1. Es ist jetzt 01:55 Uhr.
Seite 3 von 4     123 4      

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