Entwickler-Ecke
Dateizugriff - Auslesen einer Datei
Lestat - Di 25.03.03 14:28
Titel: Auslesen einer Datei
Hallo Leutz,
hab ne Problemstellung bekommen und weiß nicht wie ich anfangen soll.
Also,
ich soll einen Buchstaben eingeben in eine Datei reinschauen und zählen wir oft dieser Buchstabe in der Datei vorhanden ist!
danke
ShadowCaster - Di 25.03.03 14:40
Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20:
| function zaehleBuchstabe(DateiName : String; Buchstabe : Char) : Integer; var i : Integer; Ms1 : TMemoryStream; VergleichChar : Char; begin Result := 0; Ms1 := TMemoryStream.Create; if FileExists(DateiName) then begin Ms1.LoadFromFile(DateiName); for i := 0 to Ms1.Size - 1 do begin Ms1.Read(VergleichChar, 1); if VergleichChar = Buchstabe then inc(Result); end; end; Ms1.Destroy; end; |
Das ist die Funktion, um den Buchstaben zu zählen. Ist eigentlich sehr einfach. Ich habs über einen Memorystream gemacht, weil das am einfachsten und schnellsten geht, solange die Datei nicht über 100 MB groß ist ;)
Um die Funktion zu verwenden, benutzt du folgenden Code:
Quelltext
1: 2: 3: 4: 5: 6:
| procedure TForm1.Button1Click(Sender: TObject); var BuchstabenCount : Integer; begin BuchstabenCount := zaehleBuchstabe('C:\Beispiel.dat', 'A') end; |
jetzt steht in der Variablen BuchstabenCount die Anzahl wie oft der Buchstabe in der Datei 'C:\Beispiel.dat' vorkommt.
Lestat - Di 25.03.03 14:51
Danke !
hat alles geklappt !
Motzi - Di 25.03.03 14:52
Erstens würde ich das ganze direkt mit dem File machen und nicht über einen MemoryStream und außerdem nicht immer nur ein einzelnes Char lesen! Dann gehts auch schneller! :wink:
Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22:
| function CountChars(const aFileName: String; c: Char): Integer; const MAX = 10240; var aStream: TFileStream; szBuffer: array [1..MAX] of Char; i, iReadCount: Integer; begin Result := 0; aStream := TFileStream.Create(aFileName, fmOpenRead or fmShareDenyWrite); try while aStream.Position < aStream.Size do begin iReadCount := aStream.Read(szBuffer, MAX); for i := 1 to iReadCount do if szBuffer[i] = c then Inc(Result); end; finally aStream.Free; end; end; |
@ShadowCaster: Zum Freigeben eines Objektes sollte übrigens Free verwendet werden! Free überprüft noch explizit ob der Self-Pointer nil ist bevor es Destroy aufruft. Dadurch wird ein EAccessViolent verhindert, wenn versucht wird ein nicht mehr existierendes Objekt freizugeben.
Lestat - Di 25.03.03 14:59
Ihr wisst bestimmt auch wie ich Char in einen String umwandeln kann, oder ?? :?
ShadowCaster - Di 25.03.03 15:00
| Zitat: |
| Erstens würde ich das ganze direkt mit dem File machen und nicht über einen MemoryStream und außerdem nicht immer nur ein einzelnes Char lesen! Dann gehts auch schneller! |
Das ist definitiv falsch! Wenn man so wie du zeichenweise einliest (Filestream - direkt von der Platte), dann dauert es doppelt bis dreifach so lang als wenn du erst komplett von der Platte in den Ram einliest und dann Zeichen für Zeichen bearbeitest. Ich hab das mal getestet und bei mir schneidet memorystream besser ab. Außerdem: wer verwendet noch filestream? Wenn schon Blockread, oder? egal
| Zitat: |
Zum Freigeben eines Objektes sollte übrigens Free verwendet werden! Free überprüft noch explizit ob der Self-Pointer nil ist bevor es Destroy aufruft. Dadurch wird ein EAccessViolent verhindert, wenn versucht wird ein nicht mehr existierendes Objekt freizugeben. |
das war mir bisher unbekannt. Die Delphileute mit denen ich bisher geredet hab, sagten mir immer, dass ein Destroy auch ein Free aufruft intern... :?: :?: Naja, egal.
Allerdings ist die Lösung mit dem Memorystream leichter verständlich, würde ich mal behaupten 8) für einen Programmieranfänger.
Achja, du solltest auch wissen, dass eine While-Schleife langsamer ist als eine For-Schleife. Wenn du kannst, dann verwende gerade bei Dateneinleseroutinen immer Forschleifen.
Wenn du eine TList mit Records von hinten ab aufräumen willst, dann ist das was anderes, dann gilt eine Whileschleife.
Und eine ganze Einleseroutine in einen Try-Except-Block zu packen... uff... :shock:
Delete - Di 25.03.03 15:02
Quelltext
1: 2: 3: 4: 5:
| var c : Char; begin c := 's'; Showmessage(String(c)); |
Den Cast mit String kannst du auch weglassen. Ich würde ihn nur machen, damit es etwas deutlicher wird, was passiert.
Lestat - Di 25.03.03 15:04
mist hab mich vertan ... ähh ... das war String in Char ... :oops:
wisst ihr das vielleicht auch ....
Delete - Di 25.03.03 15:08
Quelltext
1: 2: 3: 4: 5: 6:
| var s : String c : Char; begin s := 'Hello world'; c := s[1] |
Einfach den Index des Zeichens im String angeben. Das erste Zeichen hat den Index eins.
Delete - Di 25.03.03 15:13
| ShadowCaster hat folgendes geschrieben: |
| Zitat: | | Erstens würde ich das ganze direkt mit dem File machen und nicht über einen MemoryStream und außerdem nicht immer nur ein einzelnes Char lesen! Dann gehts auch schneller! |
Das ist definitiv falsch! Wenn man so wie du zeichenweise einliest (Filestream - direkt von der Platte), dann dauert es doppelt bis dreifach so lang als wenn du erst komplett von der Platte in den Ram einliest und dann Zeichen für Zeichen bearbeitest. Ich hab das mal getestet und bei mir schneidet memorystream besser ab. Außerdem: wer verwendet noch filestream? Wenn schon Blockread, oder? egal |
ShadowCaster, wann lernst du endlich
richtig und
aufmerksam zu lesen? Motzi hat nichts anderes gesagt als du:
| Motzi hat folgendes geschrieben: |
| ... und außerdem nicht immer nur ein einzelnes Char lesen! Dann gehts auch schneller! |
Und wenn du dir mal seinen Beispielcode anschaust, müsstest du eigentlich erkennen, dass max. 10.240 Zeichen gelesen werden (können). Die tatsächlich gelesenen Bytes (bzw. Chars) werden als Ergebnis von "Read" zurückgegeben:
Quelltext
1: 2: 3: 4: 5:
| const MAX = 10240; { ... } iReadCount := aStream.Read(szBuffer, MAX); { ... } |
Der große Vorteil bei dieser Methode ist der, dass du auch fette Dateien (mit Größen von 10meg aufwärts) relativ flott (weil: stückchenweise) bearbeiten kannst. Bei deinem Memorystream hast du so ein dickes Monster
komplett im RAM. Ob das so gut ist ...
ShadowCaster - Di 25.03.03 15:15
@MathiasSimmack: Wenn du davon ausgehst, dass ein Filestream Zeichen für Zeichen einliest, dann hat motzi was anderes gesagt wie ich! Wenn ich Blockweise einlese aus einem Filestream, dann ist das was anderes.
ShadowCaster - Di 25.03.03 15:49
Hier ist eine Methode wie ich die Aufgabe gelöst hätte. Ich glaub, schneller dürfte es nicht mehr gehen. Wenn man den Buffer 2 MB groß macht, dann liest man mindestens doppelt so schnell wie der Windows-Explorer. 1MB reicht jedoch auch.
Quelltext
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:
| function zaehleZeichen(DateiName : String; Buchstabe : Char) : Integer; var i : Integer; InFile : File; InBuffer : PChar; Gelesen : Integer; InPos : Pointer; const BufferSize = 1048576; begin Result := 0; if FileExists(DateiName) then begin GetMem(InBuffer, BufferSize); AssignFile(InFile, DateiName); Reset(InFile, 1); repeat BlockRead(InFile, InBuffer^, BufferSize, Gelesen); InPos := Pointer(InBuffer); for i := 0 to Gelesen - 1 do begin if Char(InPos^) = Buchstabe then inc(Result, 1); InPos := Pointer(Cardinal(InPos) + 1); end; until (Gelesen < BufferSize); FreeMem(InBuffer); CloseFile(InFile); end; end;
procedure TForm1.Button1Click(Sender: TObject); begin ShowMessage(IntToStr(zaehleZeichen('C:\test.txt', 'b'))); end; |
wenn jetzt noch einer was zu meckern hat, dann weiß ich auch nicht mehr.
Die Funktion funzt bei mir einwandfrei und auf meinem 1,8 Ghz-Rechner würde der bei einer 1Gigabyte-Datei die Anzahl der A's in weit weniger als einer Minute ermitteln. Aber zum Testen hab ich keinen Bock ;)
Motzi - Di 25.03.03 18:06
@ShadowCaster: Ein MemoryStream ist ja nichts anderes als ein "Workaround" um einen Datenblock im Speicher. Preisfrage: Was glaubst du wie die Methode LoadFromFile die Daten von der Festplatte in den Speicher bekommt?!? :wink:
Edit: Übrigens wegen deinem Kommentar zum FileStream und weil ich gerade gesehen habe, dass du AssignFile verwendest. Dann will ich an dieser Stelle mal Hagen Reddmann, einen Spezialisten aus dem Entwickler Forum zitieren:
| Hagen Reddmann hat folgendes geschrieben: |
Streams sind logischer, komfortabler, Objecte, kompatible untereinander, einfacher und alle guten VCL Komponenten und Objecte sollten Streams unterstützen. Sie sind nicht langsammer als BlockRead aber meistens schneller, da man ja Spezialstreams nutzen kann die intern automatisch Dateizugriffe cachen, völlig transparent für Dich. Streams sind die von Borland propagierte und unterstützte Methode !!
Streams benötigen aber einen höheren Overhead, da die Unit Classes mit eingebunden wird, Streams sind Objectorientiert also ein Schwachpunkt für denjenigen der nur prozedural programmieren kann. |
ShadowCaster - Di 25.03.03 18:46
streams verwenden vielleicht teilweise den Blockread, das ist richtig aber sie sind nicht schneller. Wenn ich doch nur den Link auf diese eine Statistikseite hätte.. ich such nochmal. Da gibt es sehr große Unterschiede und die darf man nicht unterschätzen.
Hast du dir mal die asm-Datei vom Blockread angeschaut? Das sind ein paar wenige Assembler-Befehle für den Inline-Assembler. Streams haben meistens eine Klasse als Overhead und es werden Variablen durch Funktionsaufrufe geparst.
Das ist vergleichbar wenn du eine Datenbank-Query mit oder ohne Overhead benutzt. Im Endeffekt hat die CPU (wenn auch wenige Befehle), jedoch einiges mehr abzuarbeiten, wenn du Streams benutzt. Glaub mir, ich weiß wovon ich rede, wenn ich TFileStream nicht verwende ;)
Werd mal schauen, ob ich den Link finde...
Soll jetzt kein Streitgespräch geben, ok? ich werd mal nachschauen.
Motzi - Di 25.03.03 19:09
| ShadowCaster hat folgendes geschrieben: |
| Soll jetzt kein Streitgespräch geben, ok? |
War auch nie meine Absicht! Aber für eine sachliche Diskussion bin ich immer zu haben..! :wink:
Zu BlockRead kann ich nicht viel sagen, da ich es (aus oben genannten Gründen) schon lange nicht mehr verwende.
Zum MemoryStream: die LoadFromFile Methode tut auch nichts anderes als einen TFileStream zu verwenden:
Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11:
| procedure TMemoryStream.LoadFromFile(const FileName: string); var Stream: TStream; begin Stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite); try LoadFromStream(Stream); finally Stream.Free; end; end; |
Und zu TFileStream: TFileStream ist nur eine ableitung eines THandleStreams und dieser ist wiederum nur ein Workaround um die API-Datei-Funktionen...
Zu den Statistiken: würd mich interessieren! Bitte posten falls du den Link findest! Und nachdem du mich jetzt neugierig gemacht hast werd ich selber auch mal testen!! :wink:
Delete - Di 25.03.03 19:45
| ShadowCaster hat folgendes geschrieben: |
| @MathiasSimmack: Wenn du davon ausgehst, dass ein Filestream Zeichen für Zeichen einliest, dann hat motzi was anderes gesagt wie ich! Wenn ich Blockweise einlese aus einem Filestream, dann ist das was anderes. |
10.240 Zeichen auf einen Schlag ist, IMHO, ein Block. Und damit hat Motzi nichts anderes gesagt als du. Aber ich merke, du bist lernfähig. :)
btw: Ich habe auch einen kleinen Test gemacht. Meine Datei hatte allerdings eine Größe von 90.882.573 Bytes (~90meg) hat. Interessanterweise lag der Filestream (mit einem 128k Puffer) mit durchschnittlich 13 Sekunden vor dem Memorystream mit durchschnittlich 19 Sekunden. Gefunden haben beide die 349.583 A's.
Aber das muss nichts heißen. Mit einer anderen Systemkonfiguration oder auch mit einer anderen Datei könnte das Ergebnis durchaus anders aussehen.
wulfskin - Di 25.03.03 19:46
Hallo ShadowCaster!
Diese Statistiksetie interessiert mich auch! Im alten DF habe ich auch mal gepostet, dass ein BlockRead schneller ist, da ich es mit TFIleStream verglichen habe. Am Ende war es so, das TMemoryStream schneller als BlockRead war.
Also ich sage BlockRead ist so schnell wie Streams. Aber dafür sind Streams einfacher!
Gruß wulfskin!!
Motzi - Di 25.03.03 20:36
Ich hab mir jetzt mal die Sourcen vom TMemoryStream angeschaut. Es wird zuerst der entsprechende Speicher am Heap reserviert und dann die ganze Datei in einem Schub eingelesen. Nachteil bei der Sache: bei einer sehr großen Datei landet diese beim einlesen sofort wieder in der Swap-Datei.
Und warum ich der Meinung bin, dass ein MemoryStream in diesem Fall eher ungeeignet ist. Sofern die Daten nicht in der Swap-Datei liegen, kann man auf Daten im RAM zwar schneller zugreifen als von der Platte, aber: Zuerst wird die Datei von der Festplatte in den Speicher geladen (erstes mal Lesen). Dann werden die Daten aus dem MemoryStream ausgelesen und durchgecheckt (2tes mal Lesen). 2 mal Lesen.. irgendwie is das also doppelt gemoppelt...
Motzi - Di 25.03.03 20:52
Also.. ich hab jetzt mal getestet und bei mir sind FileStreams und AssignFile ungefähr gleich schnell.. ist auch logisch, weil ich hab mir BlockRead mal im CPU-Fenster angeschaut und das macht im Prinzip nichts anderes als der FileStream (genau genommen der THandleStream) nämlich ReadFile aus der Kernel32.dll aufzurufen...
Motzi - Di 25.03.03 21:23
Wow, mein drittes Posting hintereinander! :wink:
Hab jetzt den Test auch noch mit TMemoryStream gemacht.. (LoadFromFile und dann dieselbe Suchroutine) Und das Ergebnis hat mich nicht wirklich überrascht! Mit dem TMemoryStream war das ganze um bis zu 18x langsamer!
Getest hab ich das ganze mit einer 50MB Datei...
ShadowCaster - Mi 26.03.03 11:20
Erstmal sorry. Den Link konnte ich nicht mehr auftreiben... :( Die Site gibt es wohl nicht mehr.
Motzi, ich kann dir nur recht geben. Memorystream ist in jedem Falle ungeeignet. Ich verwende ihn nurnoch wenns schnell gehen soll oder bei kleinen Dateien (sehr klein), da sonst alles eh wieder auf der Platte (swap) und nicht im Ram landet. Eigentlich müsste Memorystream in
MemorySwapStream umbenannt werden.
naja, verwende mal Blockread :) Das ist sau schnell.
Hier der AsmCode von Blockread (Blockread in delphi wird nicht aus der WinApi verwendet. Das ist ne eigene Routine). Blockread hat aber einen Nachteil. Wenn du von einer CD auf die Platte liest, ist er minimal langsamer als der Api-Befehl aber auf der Platte bis zu doppelt so schnell.
Quelltext
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:
| ; ******************************************************* ; * * ; * Delphi Runtime Library * ; * * ; * Copyright (c) 1996,98 Inprise Corporation * ; * * ; *******************************************************
INCLUDE SE.ASM INCLUDE FILEIO.ASM
.386 .MODEL FLAT
EXTRN ReadFile:NEAR, GetLastError:NEAR, SetInOutRes:NEAR
PUBLIC _BlockRead
.CODE
; PROCEDURE _BlockRead( f: File; buffer: Pointer; recCnt: Longint; var result: Longint);
_BlockRead PROC
; -> EAX Pointer to file variable ; EDX Pointer to buffer ; ECX Number of records to read ; [ESP+4] Pointer to result or nil
PUSH EBX PUSH ESI
MOV EBX,EAX MOV ESI,EDX
MOV EDX,[EBX].Mode ; File must be open SUB EDX,fmInput CMP EDX,fmInOut-fmInput JA @@fileNotOpen
; ReadFile(f.Handle, buffer, recCnt*f.RecSize, @result, Nil);
PUSH ECX
PUSH 0 ; space for OS result MOV EAX,ESP
PUSH 0 ; pass lpOverlapped PUSH EAX ; pass @result
MOV EAX,[EBX].RecSize MUL ECX PUSH EAX ; pass nNumberOfBytesToRead
PUSH ESI ; pass lpBuffer PUSH [EBX].Handle ; pass hFile CALL ReadFile DEC EAX ; check EAX = TRUE POP EAX ; pop result POP ECX JNZ @@error
XOR EDX,EDX DIV [EBX].RecSize ; recCnt = result DIV f.RecSize MOV EDX,[ESP+4+8] TEST EDX,EDX JE @@noResult MOV [EDX],EAX @@exit: POP ESI POP EBX
RET 4
@@noResult: CMP EAX,ECX JE @@exit MOV EAX,100 JMP @@errExit
@@error: CALL GetLastError @@errExit: CALL SetInOutRes XOR EAX,EAX JMP @@exit
@@fileNotOpen: MOV EAX,103 JMP @@errExit
_BlockRead ENDP
END |
Motzi - Mi 26.03.03 12:01
| ShadowCaster hat folgendes geschrieben: |
| naja, verwende mal Blockread :) Das ist sau schnell. |
Hab ich, war ungefähr genauso schnell wie ein FileStream! :)
| Zitat: |
| Hier der AsmCode von Blockread (Blockread in delphi wird nicht aus der WinApi verwendet. Das ist ne eigene Routine). Blockread hat aber einen Nachteil. Wenn du von einer CD auf die Platte liest, ist er minimal langsamer als der Api-Befehl aber auf der Platte bis zu doppelt so schnell. |
Doch, im Endeffekt landet immer alles in der API! Debug einen BlockRead-Aufruf mal im CPU-Fenster, du wirst sehen früher oder später landest du bei KERNEL32.ReadFile!
Sieht man ja auch schon im Code:
Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21:
| EXTRN ReadFile:NEAR <---
{...} ; ReadFile(f.Handle, buffer, recCnt*f.RecSize, @result, Nil);
PUSH ECX
PUSH 0 ; space for OS result MOV EAX,ESP
PUSH 0 ; pass lpOverlapped PUSH EAX ; pass @result
MOV EAX,[EBX].RecSize MUL ECX PUSH EAX ; pass nNumberOfBytesToRead
PUSH ESI ; pass lpBuffer PUSH [EBX].Handle ; pass hFile CALL ReadFile <--- {...} |
ShadowCaster - Mi 26.03.03 12:21
das ist richtig aber das geht über den Kernel und nicht über irgendwelche externen Api-Dll's ;) Naja Filestream und Blockread müsste eigentlich fast genauso schnell wie AssignFile und Blockread sein. Da hast du recht. Wenn man von den paar extrabefehlen im Filestream absieht, die die CPU abarbeiten muss, müsste er mit blockread gleich schnell sein.
Nur wofür Filestream und blockread wenn ich doch gleich meinen Speicher selbst verwalten kann und kein Filestreamobjekt brauche? Ich mags eigentlich lieber wenn ich sehe, was hinter den Aufrufen im Code steht.
Aber egal. :roll:
Hauptsache, man erziehlt den besten Erfolg. :wink:
ShadowCaster - Mi 26.03.03 13:33
ach f..... :? auch egal.. .;)
Jedenfalls glaub ich, dass nach der Diskussion in dem Thread hier bald keine Fragen mehr offen sind zum Thema streams... *LOL*
Motzi - Mi 26.03.03 13:43
| ShadowCaster hat folgendes geschrieben: |
| Jedenfalls glaub ich, dass nach der Diskussion in dem Thread hier bald keine Fragen mehr offen sind zum Thema streams... *LOL* |
Da hast du allerdings recht...! :wink:
ShadowCaster - Mi 26.03.03 14:09
aber ne Menge dazugelernt hab ich hier in diesem Forum dank euch schon. THX goes to Auq :wink:
mimi - So 30.03.03 22:10
mal ne Frage: warum ist While langsammer als eine For schleife ???
das ist mir neu : :shock:
ShadowCaster - Mo 31.03.03 12:29
hier ein Auszug aus der Delphi-Hilfe:
| Zitat: |
for...to-Anweisung hat starke Ähnlichkeit mit dem folgenden while-Konstrukt:
begin
Zähler := Anfangswert;
while Zähler <= Endwert do
begin
Anweisung;
Zähler := Succ(Zähler);
end;
end
Im Unterschied zur for...to-Anweisung wird Endwert aber in der while-Schleife vor jedem Durchlauf erneut ausgewertet. Wenn Endwert ein komplexer Ausdruck ist, kann dies eine Verlangsamung der Ausführungsgeschwindigkeit zur Folge haben. Außerdem können sich von Anweisung verursachte Änderungen an Endwert auf die Ausführung der Schleife auswirken.
|
:wink:
mimi - Mo 31.03.03 17:08
wieder mal was neues gelehrnt, ich nutze selten eine weile schleife am liebsten sind mir diese reper(until) schleifen irgenwie- aber egal...
ShadowCaster - Di 01.04.03 08:36
es ist auch eigentlich egal :) wenn du die Whileschleife dazu benutzt, um die Blöcke der Datei einzulesen oder repeat-until und die For-Schleife für die Blöcke durchzugehen, kann nichts schief gehen :) :wink:
mimi - Di 01.04.03 17:56
Doch es kann vieles schief gehen z.b. das man vergies eine Break anweisung einzusetztne in einer While schleife :)
naja egal...
Entwickler-Ecke.de based on phpBB
Copyright 2002 - 2011 by Tino Teuber, Copyright 2011 - 2026 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!