7 Stimmen

Wie kann ich effizient die ersten Zeilen vieler Dateien in Delphi lesen

Ich habe eine "Dateien suchen" Funktion in meinem Programm, die Textdateien mit dem Suffix .ged findet, die mein Programm liest. Die gefundenen Ergebnisse werden in einem Explorer-ähnlichen Fenster angezeigt, das so aussieht:

Bildbeschreibung eingeben

Ich verwende die Standardmethoden FindFirst / FindNext, und dies funktioniert sehr schnell. Die oben gezeigten 584 Dateien werden innerhalb weniger Sekunden gefunden und angezeigt.

Was ich jetzt tun möchte, ist zwei Spalten zur Anzeige hinzuzufügen, die die "Quelle" und "Version" enthalten, die in jeder dieser Dateien enthalten sind. Diese Informationen befinden sich in der Regel in den ersten 10 Zeilen jeder Datei, auf Zeilen, die so aussehen:

1 SOUR FTM
2 VERS Family Tree Maker (20.0.0.368)

Ich habe kein Problem damit, dies sehr schnell selbst zu analysieren, und das ist nicht, was ich frage.

Was ich Hilfe benötige, ist einfach, wie ich die ersten 10 oder so Zeilen aus diesen Dateien am schnellsten laden kann, damit ich sie analysieren kann.

Ich habe versucht, ein StringList.LoadFromFile zu verwenden, aber es dauert zu lange, um die großen Dateien zu laden, wie diejenigen über 1 MB.

Da ich nur die ersten 10 Zeilen oder so benötige, wie könnte ich am besten an sie herankommen?

Ich verwende Delphi 2009, und meine Eingabedateien können Unicode sein oder auch nicht, daher muss dies für jede Codierung funktionieren.


Nachfrage: Danke Antonio,

Ich habe schließlich das hier gemacht, das gut funktioniert:

var
  CurFileStream: TStream;
  Buffer: TBytes;
  Value: string;
  Encoding: TEncoding;

try
  CurFileStream := TFileStream.Create(folder + FileName, fmOpenRead);
  SetLength(Buffer, 256);
  CurFileStream.Read(Buffer[0], 256);
  TEncoding.GetBufferEncoding(Buffer, Encoding);
  Value := Encoding.GetString(Buffer);
  ...
  (analysiere Value, um das zu erhalten, was ich will)
  ...
finally
  CurFileStream.Free;
end;

14voto

Antonio Bakula Punkte 19627

Verwenden Sie TFileStream und lesen Sie mit der Methode Read die erforderliche Anzahl von Bytes. Hier ist ein Beispiel zum Lesen von Bitmap-Informationen, die auch am Anfang der Datei gespeichert sind.

http://www.delphidabbler.com/tips/19

4voto

Cray Punkte 2266

Öffnen Sie einfach die Datei selbst zum Blocklesen (ohne Verwendung der integrierten Funktionalität von TStringList) und lesen Sie den ersten Block der Datei. Anschließend können Sie diesen Block beispielsweise mit strings.SetText() in einen StringList laden (wenn Sie Blockfunktionen verwenden) oder einfach strings.LoadFromStream() verwenden, wenn Sie Ihre Blöcke über Streams laden.

Persönlich würde ich einfach die FileRead/FileWrite-Blockfunktionen verwenden und den Block in einen Puffer laden. Sie könnten auch ähnliche WinAPI-Funktionen verwenden, aber das wäre nur mehr Code ohne Grund.

Das Betriebssystem liest Dateien in Blöcken, die auf fast jeder Plattform/dem Dateisystem mindestens 512 Byte groß sind. Sie können also zuerst 512 Bytes lesen (und hoffen, dass Sie alle 10 Zeilen erhalten, was zutrifft, wenn Ihre Zeilen im Allgemeinen kurz genug sind). Das ist praktisch genauso schnell wie das Lesen von 100 oder 200 Bytes.

Dann, wenn Sie feststellen, dass Ihre Strings-Objekte weniger als 10 Zeilen enthalten, lesen Sie einfach den nächsten 512-Byte-Block und versuchen es erneut zu parsen. (Oder gehen Sie einfach mit 1024, 2048 und so weiter großen Blöcken, auf vielen Systemen wird es wahrscheinlich genauso schnell sein wie 512-Blöcke, da die Clustergrößen des Dateisystems im Allgemeinen größer als 512 Bytes sind).

PS. Außerdem könnten Sie durch die Verwendung von Threads oder asynchroner Funktionalität in WinAPI-Dateifunktionen (CreateFile und dergleichen) diese Daten von Dateien asynchron laden, während der Rest Ihrer Anwendung läuft. Speziell wird die Benutzeroberfläche während des Lesens großer Verzeichnisse nicht einfrieren.

Das Laden Ihrer Informationen wird dadurch schneller erscheinen (da die Dateiliste direkt geladen wird und dann einige Millisekunden später der Rest der Informationen erscheint), ohne die tatsächliche Lese-Geschwindigkeit zu erhöhen.

Tun Sie dies nur, wenn Sie die anderen Methoden ausprobiert haben und das Gefühl haben, dass Sie einen zusätzlichen Schub benötigen.

3voto

Remy Lebeau Punkte 498719

Sie können einen TStreamReader verwenden, um einzelne Zeilen aus einem beliebigen TStream-Objekt, wie z.B. einem TFileStream, zu lesen. Für noch schnellere Datei-E/A-Vorgänge könnten Sie Memory-Mapped Views mit TCustomMemoryStream verwenden.

2voto

Warren P Punkte 61510

Okay, ich habe meine erste Antwort gelöscht. Mit Remys erster obiger Vorschlag habe ich es noch einmal mit integrierten Funktionen versucht. Was mir hier nicht gefällt, ist dass man zwei Objekte erstellen und freigeben muss. Ich würde meine eigene Klasse erstellen, um das zu verpacken:

var
  fs:TFileStream;
  tr:TTextReader;
  filename:String;
begin
  filename :=  'c:\temp\textFileUtf8.txt';
  fs := TFileStream.Create(filename, fmOpenRead);
  tr := TStreamReader.Create(fs);
  try
      Memo1.Lines.Add( tr.ReadLine );

  finally
    tr.Free;
    fs.Free;
  end;   
end;

Wenn sich jemand dafür interessiert, was ich hier vorher hatte, hatte es das Problem, nicht mit Unicode-Dateien zu funktionieren.

0voto

Wouter van Nifterick Punkte 22981

Manchmal ist der alte Pascal-Stil gar nicht so schlecht. Auch wenn der nicht-OO-Dateizugriff nicht mehr sehr beliebt zu sein scheint, funktioniert ReadLn(F,xxx) immer noch ziemlich gut in Situationen wie Ihrer.

Der folgende Code lädt Informationen (Dateiname, Quelle und Version) in ein TDictionary, damit Sie leicht darauf zugreifen können. Alternativ können Sie auch einen Listview im virtuellen Modus verwenden und Dinge in dieser Liste nachschlagen, wenn das ondata Ereignis ausgelöst wird.

Warnung: Der folgende Code funktioniert nicht mit Unicode.

program Project101;
{$APPTYPE CONSOLE}

uses
  IoUtils, Generics.Collections, SysUtils;

type
  TFileInfo=record
    FileName,
    Source,
    Version:String;
  end;

function LoadFileInfo(var aFileInfo:TFileInfo):Boolean;
var
  F:TextFile;
begin
  Result := False;
  AssignFile(F,aFileInfo.FileName);
  {$I-}
  Reset(F);
  {$I+}
  if IOResult = 0 then
  begin
    ReadLn(F,aFileInfo.Source);
    ReadLn(F,aFileInfo.Version);
    CloseFile(F);
    Exit(True)
  end
  else
    WriteLn('Could not open ', aFileInfo.FileName);
end;

var
  FileInfo:TFileInfo;
  Files:TDictionary;
  S:String;
begin
  Files := TDictionary.Create;
  try
    for S in TDirectory.GetFiles('h:\WINDOWS\system32','*.xml') do
    begin
      WriteLn(S);
      FileInfo.FileName := S;
      if LoadFileInfo(FileInfo) then
        Files.Add(S,FileInfo);
    end;

    // Dateiinformationen anzeigen...
    for FileInfo in Files.Values do
      WriteLn(FileInfo.Source, ' ',FileInfo.Version);
  finally
    Files.Free
  end;
  WriteLn;
  WriteLn('Fertig. Drücken Sie eine beliebige Taste zum Beenden...');
  ReadLn;
end.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X