Delphi-PRAXiS
Seite 1 von 2  1 2      

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Win32/Win64 API (native code) (https://www.delphipraxis.net/17-win32-win64-api-native-code/)
-   -   Datei im Speicher zeilenweise lesen (https://www.delphipraxis.net/54819-datei-im-speicher-zeilenweise-lesen.html)

Luckie 12. Okt 2005 03:33


Datei im Speicher zeilenweise lesen
 
Ich lade so:
Delphi-Quellcode:
var
  hMemory      : THandle;
  pMemory      : pointer;
begin
  hFile := CreateFile(szFilename,GENERIC_READ or GENERIC_WRITE,
    FILE_SHARE_READ or FILE_SHARE_WRITE,nil,OPEN_EXISTING,
    FILE_ATTRIBUTE_NORMAL,0);

  // Speicher anfordern, & Dateiinhalt lesen
  hMemory := GlobalAlloc(GMEM_MOVEABLE or GMEM_ZEROINIT,MEMSIZE);
  pMemory := GlobalLock (hMemory);

  ReadFile(hFile,pMemory^,MEMSIZE-1,SizeReadWrite,nil);
eine Datei in den Speicher. Es handelt sich dabei um eine Textdatei, die ich nun zeilenweise lesen muss. Wie kann ich das nun am einfachsten bewerkstelliegen ohne jedes Byte einzeln zu lesen und nach einem Zeilenumbruch zu suchen? Wichjtig: Es muss mit WinAPI Funktionen geschehen, das heißt eine StringListe oder ähnliches kann ich nicht benutzen.

Hintergrund ist der, dass die Datei auch größer sein kann und ich nicht für jede Zeile lesend auf den Datenträger zugreifen will, da dies zum einen sehr langsam ist, das Betriebssystem sonst andauernd mit lesen vom Datenträger beschäftigt wäre und bei Notebooks kommt hinzu, dass der dauernde Zugriff auf den Datenträger sehr viel Strom verbraucht. Deshalb kann ich auch nicht mit AssignFile und Readln arbeiten.

BrunoT 12. Okt 2005 06:20

Re: Datei im Speicher zeilenweise lesen
 
Hi Luckie,

lies es doch gleich in eine verkettete Liste Zeilenweise ein.

mfg

BrunoT

Flocke 12. Okt 2005 07:29

Re: Datei im Speicher zeilenweise lesen
 
Um es dir einfach zu machen, könntest du wirklich einfach ein Text bzw. TextFile mit ReadLn benutzen. Die Puffergröße kannst du mit System.SetTextBuf vergrößern (z.B. auf 64K). Das sollte die Datenträgerzugriffe auf ein Minimum beschränken.

Ansonsten müsstest du dir wirklich jedes Zeilenende selbst suchen. Würde natürlich zur Not auch gehen, indem du direkt einen String als Puffer nimmst, also etwa so (ungetestet):

Delphi-Quellcode:
const
  BUFSIZE = 65536;

// Initialisierung
function InitLine(hFile: THandle; var FileBufStr: string): boolean;
var
  GotBytes: cardinal;
begin
  SetLength(FileBufStr, BUFSIZE);
  Result := ReadFile(hFile, @FileBufStr[1], BUFSIZE, GotBytes, nil);
  if Result then
    SetLength(FileBufStr, GotBytes)
  else
    FileBufStr := '';
end;

// Zeile lesen nach Output, liefert FALSE bei EOF, sonst TRUE
function GetLine(hFile: THandle; var FileBufStr: string; var Output: string): boolean;
var
  P, OldLen: integer;
  GotBytes: cardinal;
begin
  P := Pos(#10, FileBufStr);
  while P = 0 do
  begin
    OldLen := Length(FileBufStr);
    SetLength(FileBufStr, OldLen + BUFSIZE);
    if not ReadFile(hFile, @FileBufStr[OldLen], BUFSIZE, GotBytes, nil) then
      break;
    if GotBytes = 0 then
      break;
    SetLength(FileBufStr, OldLen + GotBytes);
    P := Pos(#10, FileBufStr);
  end;

  if P > 0 then
  begin
    Result := true;
    Output := Copy(FileBufStr, 1, P - 1);
    Delete(FileBufStr, 1, P);
  end
  else
  begin
    Result := FileBufStr <> '';
    Output := FileBufStr;
    FileBufStr := '';
  end;
end;

ripper8472 12. Okt 2005 07:47

Re: Datei im Speicher zeilenweise lesen
 
die c standardfunktionen fuer zeilenweises lesen lesen auch nur zeichen fuer zeichen.
du koenntest das aber optimieren, wenn du ein bisschen mit zeigern arbeitest.
zeiger auf den anfang setzen und dann immer weitergehen bis ein umbruch kommt. dann von anfang bis hier als zeile betrachten.
dann das gleiche spiel nochmal.

alzaimar 12. Okt 2005 08:21

Re: Datei im Speicher zeilenweise lesen
 
Entschuldigt mal bitte, was soll den dieses 'lesen auch nur Zeichen für Zeichen'. Das macht doch keine Funktion mehr: Windows sorgt dafür, das die Daten blockweise eingelesen werden (üblicherweise in Größe der Systempage, also 8kb). Das ist so ziemlich optimal. Wenn ich dann bis zum nächsten Zeilenende lesen will, dann bleibt mir ja wohl nichts anderes übrig, als Zeichen für Zeichen zu vergleichen. Windows sorgt z.B. bei einem Stream schon dafür, das das recht flott geht.

Damit deine SW stabil bleibt, musst Du eine kleine Mittelschicht implementieren, die aus Windows-Seiten einzelne Zeilen macht. Wenn Du weisst, das eine Zeile maximal 1000 Zeichen lang ist, dann hast Du zwei Buffer a 1000 Zeilen, die Du abwechselnd nachliest:
Wenn Buffer A 'fast' am Ende ist, lässt Du die folgende Seite in Buffer 'B' einlesen und umgekehrt.
Die beiden Buffer liegen im Speicher aber direkt hintereinander, sodass man A und B als ein Array implementiert. BufferA zeigt dann z.B. auf Array[0] und BufferB auf Array[1000].

Du hast dann noch eine (Inline-) Funktion GetChar, die dir Array[X] zurückliefert, X erhöht und ggf A bzw. B nachlädt.

Das wars. Du merkst Dir also 'X', liest per GetChar bis zum nächsten CR/LF und kopierst Array [Xstart]...Array [X-2] in dein Resultatstring. Dabei musst Du nur darauf achten, ob X<XStart ist, dann sind es nämlich zwei Move-Operationen, ansonsten nur eine.

BrunoT 12. Okt 2005 08:55

Re: Datei im Speicher zeilenweise lesen
 
Liste der Anhänge anzeigen (Anzahl: 2)
Hi Luckie,

ich will mal für meinen Lösungsvoeschlag ein Beispiel anhängen:

Delphi-Quellcode:
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}
type
  Zeiger = ^tZeiger;
  tZeiger = record
    Nr:Integer;
    Str: string;
    Ptr: zeiger;
  end;
var
  StartZ, HilfPtr1,HilfPtr2: Zeiger;

procedure in_Liste(i:Integer;s: string);
begin
  New(HilfPtr1);
  if i=0 then
  Begin
    New(HilfPtr2);
    StartZ:=HilfPtr2;
  end;
  HilfPtr1^.Nr := i;
  HilfPtr1^.Str := s;
  HilfPtr2^.Ptr := HilfPtr1;
  HilfPtr2:=HilfPtr1;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  T: Textfile;
  s: string;
  i:integer;
begin
  i:=0;
  memo1.Clear;
  Assignfile(t, 'test.txt');
  reset(t);
  while not eof(t) do
  begin
    readln(t, s);
    in_Liste(i,s);
    inc(i);
  end;
  closefile(t);
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  HilfPtr1 := StartZ;
  while (HilfPtr1 <> nil) do
  begin
    HilfPtr1 := HilfPtr1^.Ptr;
    if HilfPtr1 <> nil then
      Memo1.Lines.Add(HilfPtr1.Str);
  end;
end;

end.
///Der einzige "Nachteil" ist, dass die Textdatei von hinten nach vorn im Speicher liegt.
[edit] ich habe es jetzt umgedreht. Die Daten liegen jetzt in der richtigen Reihenfolge [/edit] :mrgreen:
//Aber das lässt sich ja ändern.

mfg

BrunoT

Olli 12. Okt 2005 10:18

Re: Datei im Speicher zeilenweise lesen
 
Micha, es ist völlig in Ordnung wenn du zeichenweise einliest. Wie oben erwähnt, tun dies andere Libs auch. Damit wird die Performance nicht wirklich gesenkt ;)

Luckie 12. Okt 2005 11:23

Re: Datei im Speicher zeilenweise lesen
 
Zitat:

Zitat von Flocke
Um es dir einfach zu machen, könntest du wirklich einfach ein Text bzw. TextFile mit ReadLn benutzen. Die Puffergröße kannst du mit System.SetTextBuf vergrößern (z.B. auf 64K). Das sollte die Datenträgerzugriffe auf ein Minimum beschränken.

Ansonsten müsstest du dir wirklich jedes Zeilenende selbst suchen. Würde natürlich zur Not auch gehen, indem du direkt einen String als Puffer nimmst, also etwa so (ungetestet):

Jetzt bringst du mich ins Grübeln. Dein erster Vorschlag ist natürlich der einfachere zum Umsetzen. Aber du hast dir schon deie Mühe gemacht für deinen zweiten Vorschlag kompletten Code zu schreiben (Wäre übrigens nicht nötig gewesen, das hätte ich nämlich auch noch hinbekommen. ;) Es ging mir nur um das Prinzip,), den will ich jetzt irgendwie auch nicht wegschmeißen. Mach mir die Entscheidung leichter und sagt mir einfach, dass die WinAPI Lösung, gegenüber der Pascal Lösung, performanter ist, dann nehme ich deinen schönen Code.

opfer.der.genauigkeit 12. Okt 2005 11:58

Re: Datei im Speicher zeilenweise lesen
 
Hallo Luckie,

schau dir mal das an:

FileMapping:

http://msdn.microsoft.com/library/de...ilemapping.asp

evtl. ist das interessant für dich.

Olli 12. Okt 2005 11:59

Re: Datei im Speicher zeilenweise lesen
 
Zitat:

Zitat von Luckie
Mach mir die Entscheidung leichter und sagt mir einfach, dass die WinAPI Lösung, gegenüber der Pascal Lösung, performanter ist, dann nehme ich deinen schönen Code.

Die dürften sich nicht viel nehmen. Aber da du die Datei mappen würdest und entsprechend als langen String im Speicher hast, kannst du auch mit einer Schleife durchgehen und die Zeilen extrahieren. Ich würde es übrigens wie folgt machen:

Ich gehe durch den Puffer und ersetze alle #13 durch #0, danach habe ich ein typisches "Array" von nullterminierten Strings und muß ähnliche Methoden anwenden wie bei REG_MULTI_SZ. Wenn der Puffer R/O ist, dann würde ich selbstverständlich nur nach #13 suchen ohne zu ersetzen und dann eben die entsprechenden Anfangsoffsets und Endoffsets merken und mit lstrcpyn() kopieren.

Was du keinesfalls machen solltest ist, einen String zu nehmen und die Zeichen jeweils zeichenweise an den String anzuhängen. Dadurch würde intern viel mehr ablaufen als das einfach Kopieren einer Zeile in einen String oder Puffer.


Alle Zeitangaben in WEZ +1. Es ist jetzt 05:23 Uhr.
Seite 1 von 2  1 2      

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