Entwickler-Ecke
Dateizugriff - 0-Termininierten String aus Datei lesen
Flamefire - Sa 05.06.10 13:34
Titel: 0-Termininierten String aus Datei lesen
Ich will einen String aus einer strukturierten Datei lesen. Der hat eine Variable länge (0-x) und endet mit einer 0.
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,1) or (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?
Delete - 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 - 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:
http://www.delphi-forum.de/viewtopic.php?p=567859#567859
Interessant ist dabei die Datei Source\Units\FileAccessUtils.pas, in der ich das umgesetzt habe. Der Quelltext sieht so aus:
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:
| ... 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
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, 0, 0, nil); 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));
LineLen := Integer(CurData) - Integer(StartPointer); if FUnicodeFile then begin if (FBufferPos = 0) and (FPosInBuffer = 0) then 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 - 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 [
http://www.delphi-forum.de/viewtopic.php?p=607765#607765] 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 - 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.
jaenicke - So 06.06.10 10:28
Flamefire hat folgendes geschrieben : |
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.
Flamefire hat folgendes geschrieben : |
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.:
http://www.delphi-forum.de/viewtopic.php?p=607865#607865
Flamefire - 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 - So 06.06.10 22:21
Flamefire hat folgendes geschrieben : |
@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:
http://www.delphi-forum.de/viewtopic.php?p=607948#607948
Entwickler-Ecke.de based on phpBB
Copyright 2002 - 2011 by Tino Teuber, Copyright 2011 - 2025 by Christian Stelzmann Alle Rechte vorbehalten.
Alle Beiträge stammen von dritten Personen und dürfen geltendes Recht nicht verletzen.
Entwickler-Ecke und die zugehörigen Webseiten distanzieren sich ausdrücklich von Fremdinhalten jeglicher Art!