Autor Beitrag
Flamefire
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 1207
Erhaltene Danke: 31

Win 10
Delphi 2009 Pro, C++ (Visual Studio)
BeitragVerfasst: Sa 05.06.10 13:34 
Ich will einen String aus einer strukturierten Datei lesen. Der hat eine Variable länge (0-x) und endet mit einer 0.
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
function ReadString(const hFile: THandle):String;
var c:AnsiChar;
begin
  Result:='';
  repeat
    if(not ReadF(hFile,c,1or (c=#0)) then break;
    Result:=Result+String(c);
  until false;
end;

geht zwar, ist aber ineffektiv.
Wenn ich mehr auf einmal lese, muss ich aber den FilePointer umsetzen u.ä.
Was wäre die beste Lösung für das Problem?
Luckie
Ehemaliges Mitglied
Erhaltene Danke: 1



BeitragVerfasst: Sa 05.06.10 18:51 
Den FilePointer setzen macht Windows auch, sonst könnte man ja die Datei nicht lesen. Generell ist es performanter in größeren Stücken zu lesen und dann, in deinem Fall, die Zeichen zu zerlegen.
jaenicke
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19314
Erhaltene Danke: 1747

W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: So 06.06.10 01:04 
Ich löse das ganz gerne mit MMFs, damit lassen sich viele Millionen Buchstaben pro Sekunde aus einer Datei zeilenweise auslesen. Inklusive Findung des Zeilenumbruchs usw., genauso kannst du dann das Nullzeichen suchen.

Du findest eine entsprechende Unit in meinem Registryeditor:
www.delphi-forum.de/....php?p=567859#567859
Interessant ist dabei die Datei Source\Units\FileAccessUtils.pas, in der ich das umgesetzt habe. Der Quelltext sieht so aus:
ausblenden volle Höhe FileAccessUtils.pas
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
132:
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
143:
144:
145:
146:
147:
148:
149:
150:
151:
152:
153:
154:
155:
156:
157:
158:
159:
160:
161:
162:
{
  ***** BEGIN LICENSE BLOCK *****
  Version: MPL 1.1/GPL 2.0/LGPL 2.1
  ...
}

...
type
  TFileReader = class
  private
    FPointer: Pointer;
    FFile, FMapping: THandle;
    FSize, FBufferPos, FPosInBuffer: Int64;
    FBuffer, FAllocationGranularity: Cardinal;
    FUnicodeFile: Boolean;
    function GetPosition: Int64;
    procedure SetBuffer(const Value: Cardinal);
    procedure SetPosition(const Value: Int64);
    procedure ReInitView;
  public
    constructor Create(AFileName: string; AInit: Boolean = True);
    destructor Destroy; override;
    procedure ReadLn(var AValue: string);
    property Size: Int64 read FSize;
    property Buffer: Cardinal read FBuffer write SetBuffer;
    property Position: Int64 read GetPosition write SetPosition;
  end;

implementation

{ TFileReader }

constructor TFileReader.Create(AFileName: string; AInit: Boolean = True);
var
  SysInfo: _SYSTEM_INFO;
begin
  FFile := CreateFile(PChar(AFileName), GENERIC_READ, FILE_SHARE_READ, nil,
    OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
  if FFile = 0 then
    raise Exception.Create('')
  else
  begin
    FSize := FileSeek(FFile, Int64(0), Ord(soEnd));
    FMapping := CreateFileMapping(FFile, nil, PAGE_READONLY, 00nil);
  end;
  GetSystemInfo(SysInfo);
  FAllocationGranularity := SysInfo.dwAllocationGranularity;
  FBufferPos := 0;
  if FSize >= 224 * FAllocationGranularity then
    FBuffer := 224 * FAllocationGranularity
  else if FSize >= 16 * FAllocationGranularity then
    FBuffer := 16 * FAllocationGranularity
  else if FSize >= FAllocationGranularity then
    FBuffer := FAllocationGranularity
  else
    FBuffer := FSize;
  FPosInBuffer := 0;
  if AInit then
    ReInitView;
end;

destructor TFileReader.Destroy;
begin
  CloseHandle(FFile);
  inherited;
end;

procedure TFileReader.ReInitView;
begin
  if Assigned(FPointer) then
    UnmapViewOfFile(FPointer);
  FPointer := MapViewOfFile(FMapping, FILE_MAP_READ, Int64Rec(FBufferPos).Hi,
    Int64Rec(FBufferPos).Lo, FBuffer);
  if FBufferPos = 0 then
    FUnicodeFile := (FSize > SizeOf(Word)) and (PWord(FPointer)^ = $FEFF);
end;

procedure TFileReader.SetBuffer(const Value: Cardinal);
begin
  FBuffer := Succ(Value div FAllocationGranularity) * FAllocationGranularity;
  ReInitView;
end;

function TFileReader.GetPosition: Int64;
begin
  Result := FBufferPos + FPosInBuffer;
end;

procedure TFileReader.SetPosition(const Value: Int64);
begin
  if (Value < FBufferPos) or (Value >= FBufferPos + FBuffer) then
  begin
    FBufferPos := Value - (Value mod FAllocationGranularity);
    if FSize < FBufferPos + FBuffer then
      FBuffer := FSize - FBufferPos;
    FPosInBuffer := Value mod FAllocationGranularity;
  end
  else
    FPosInBuffer := Value - FBufferPos;
  ReInitView;
end;

procedure TFileReader.ReadLn(var AValue: string);
var
  LineLen: Cardinal;
  CurValue: PByte;
  CurData, StartPointer, EndPointer: Pointer;
  RemainingLine: string;
  LineEndFound: Boolean;
  i: Integer;
begin
  if not Assigned(FPointer) then
    Exit;
  StartPointer := Pointer(Integer(FPointer) + FPosInBuffer);
  EndPointer := Pointer(Integer(FPointer) + Integer(FBuffer));
  CurData := StartPointer;
  for i := Integer(EndPointer) - Integer(CurData) - 1 downto 0 do
    if PByte(CurData)^ = $0D then
      Break
    else
      Inc(PByte(CurData));

//  while (Integer(CurData) < Integer(EndPointer)) and (PByte(CurData)^ <> $0D) do
//    Inc(PByte(CurData));
  LineLen := Integer(CurData) - Integer(StartPointer);
  if FUnicodeFile then
  begin
    if (FBufferPos = 0and (FPosInBuffer = 0then
    begin
      Inc(PByte(StartPointer), 2);
      SetString(AValue, nil, LineLen div 2 - 1);
    end
    else;
      SetString(AValue, nil, LineLen div 2);
    if Length(AValue) > 0 then
    begin
      CurValue := PByte(AValue);
      while Integer(StartPointer) < Integer(CurData) do
      begin
        CurValue^ := PByte(StartPointer)^;
        Inc(PByte(StartPointer), 2);
        Inc(CurValue);
      end;
    end;
    Inc(FPosInBuffer, 2);
  end
  else
  begin
    SetString(AValue, nil, LineLen);
    CopyMemory(PChar(AValue), StartPointer, LineLen);
  end;
  Inc(FPosInBuffer, LineLen + 2);
  if Integer(CurData) = Integer(EndPointer) then
  begin
    LineEndFound := PByte(Pred(Integer(CurData)))^ = $0D;
    Position := FBufferPos + FBuffer;
    if not LineEndFound and (Position < Size) then
    begin
      ReadLn(RemainingLine);
      AValue := AValue + RemainingLine;
    end;
  end;
end;
Du musst eben statt nach $0D nach dem Nullzeichen suchen.
Flamefire Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 1207
Erhaltene Danke: 31

Win 10
Delphi 2009 Pro, C++ (Visual Studio)
BeitragVerfasst: So 06.06.10 02:18 
Wenn ich das richtig verstanden habe, dann mappt MapViewOfFile nur Fbuffer bytes der Datei ab einer bestimmten Position.
am Anfang legst du es auf eine bestimmte Größe fest, die kleiner als die Datei sein kann. Ich würde also nicht alles lesen können, wenn z.b. meine struktur über diese größe drüber raus geht, oder?
Auch dürfte es da einen netten Bug geben: position auf ende setzen, dann wieder auf anfang: Fbuffer ist sehr klein (0?)

In einem ähnlichem Thread hier wurde gphugefile empfohlen, dürfte das nicht auch hier das Problem lösen?

BTW: Interessehalber: warum hast du die Faktoren auf 16 und 224 gesetzt, und nicht 256?
BenBE
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 8721
Erhaltene Danke: 191

Win95, Win98SE, Win2K, WinXP
D1S, D3S, D4S, D5E, D6E, D7E, D9PE, D10E, D12P, DXEP, L0.9\FPC2.0
BeitragVerfasst: So 06.06.10 02:35 
Die 16 ergibt bei 4K-Pages 64KB an Speicher. Das ist für 16 Bit die maximal adressierbare Speichermenge. Unter 32-Bit-Systemen ist das aber witziger Weise ein extrem guter Kompromiss zum Lesen von Datenblöcken, weil für die meisten Medien (Festplatten, Netzwerk, ...) 64K-Blöcke als obere Grenze recht effizient geholt werden können, ohne zu große Latenzen zu erzeugen.

Die 224 vs. 256 dürfte mit der Vermeidung von zu großen Speicherblöcken auf dem Heap zu tun haben, da dieser für kleine Blöcke optimiert ist. Da bin ich mir aber nicht ganz sicher.

_________________
Anyone who is capable of being elected president should on no account be allowed to do the job.
Ich code EdgeMonkey - In dubio pro Setting.
jaenicke
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19314
Erhaltene Danke: 1747

W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: So 06.06.10 10:28 
user profile iconFlamefire hat folgendes geschrieben Zum zitierten Posting springen:
Ich würde also nicht alles lesen können, wenn z.b. meine struktur über diese größe drüber raus geht, oder?
Doch, dann wird weitergelesen. Das ist Zeile 152 und folgende.
Mit LineEndFound wird dort geprüft ob ein Zeilenende erreicht wurde. Wenn nicht und die Position noch kleiner als die verfügbare Größe ist, dann wird erneut ReadLn angestoßen und einfach weitergelesen. Bis eben ein Zeilenumbruch erreicht oder alles eingelesen ist.
An der Stelle ließe sich auch weiter optimieren, aber da die Stringkonkatenationen in Delphi schon gut optimiert sind und der Fall bei einer guten Buffergröße nicht so oft eintreten wird, habe ich das nicht weiter verfolgt.

user profile iconFlamefire hat folgendes geschrieben Zum zitierten Posting springen:
Auch dürfte es da einen netten Bug geben: position auf ende setzen, dann wieder auf anfang: Fbuffer ist sehr klein (0?)
Das stimmt, das prüfe ich gar nicht. :shock: :oops: Ich glaube es ist sinnvoll eine temporäre Buffergröße einzuführen. Wenn die Buffergröße nicht eingehalten werden kann, wird nur diese geändert. Dann lässt sich bei der nächsten Änderung der Position das wieder auf den eigentlich gewünschten Wert setzen. Das schlägt zwei Fliegen mit einer Klappe. :mrgreen:

Ich werde mich da mal kurz ransetzen und erstens eine kleine Demo schreiben und zweitens das ganze etwas verallgemeinern (z.B. gesuchtes Endzeichen festlegbar machen).
Mit dieser Demo werde ich das dann einmal separat in Open Source veröffentlichen. Sollte inklusive Eintrag in die interne Projekt- und Buildverwaltung nicht allzu lange dauern, ich rechne einmal mit 11:30-12:00 Uhr.

// EDIT:
Na gut, ich hatte noch etwas anderes zu tun, aber hier gibts das jetzt, korrigiert und mit Demo usw.:
www.delphi-forum.de/....php?p=607865#607865
Flamefire Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 1207
Erhaltene Danke: 31

Win 10
Delphi 2009 Pro, C++ (Visual Studio)
BeitragVerfasst: So 06.06.10 22:11 
@jeanicke: und dein statement zu den 224? ;-)

was ich aber noch bräuchte wären ReadOperationen für beliebige Daten und entsprechende Schreiboperationen.
Da ich mich mit MMF nicht auskenne kurze Frage: Es dürfte doch reichen, in den Speicher zu schreiben von dem auch gelesen wird, oder? Wie sieht es da mit der Größe aus? Bei MMF müsste doch die Größe schon vorher feststehen, was sie für mich ungeeignet macht. Denn ich schreibe ja mehr oder weniger sequentiell Daten aus einer Struktur da rein.
jaenicke
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19314
Erhaltene Danke: 1747

W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: So 06.06.10 22:21 
user profile iconFlamefire hat folgendes geschrieben Zum zitierten Posting springen:
@jeanicke: und dein statement zu den 224? ;-)
Ich kann leider gar nicht weiter was dazu sagen, ich habe mit verschiedenen Werten experimentiert und damit ging es am schnellsten. ;-)
Etwas bestimmtes dabei gedacht habe ich mir also gar nicht. :mrgreen:

// EDIT:
Was das Schreiben in die Datei angeht, siehe den entsprechenden Thread, ich habe die Antwort einmal dorthin verlegt:
www.delphi-forum.de/....php?p=607948#607948