Entwickler-Ecke

Algorithmen, Optimierung und Assembler - BlockRead - Ungenau?


galagher - Fr 04.02.05 21:18
Titel: BlockRead - Ungenau?
Hallo!
Ich bastle an einem Datei-Suchprogramm, das auch Text in Dateien finden kann und die Anzahl der Vorkommen des gesuchten Textes ausgibt. Dazu lese ich die Datei mit BlockRead ein:

Delphi-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:
Buf: array [0..8192of Char;
//...
 try
  AssignFile(F, FName);
  FileMode := fmOpenRead;
   {$i-}
  Reset(F, 1);
   {$i+}

  LabeledEdit2Text := AnsiLowerCase(LabeledEdit2.Text);  //Der Suchtext

  Result := False;
  S := '';

  repeat
   Buf := '';
   BlockRead(F, Buf, SizeOf(Buf), NumRead);

   SetLength(S, Length(Buf));
   for I := 1 to Length(Buf) do
   begin
    if (Char(Buf[I]) = #0then S[I] := ' '
    else                        S[I] := Buf[I];
   end;

   S := AnsiLowerCase(S);  //Beides (Suchtext und gelesener Dateiinhalt) in Kleinbuchstaben

     //Das Ganze vergleichen
   if Pos(LabeledEdit2Text, S) > 0 then  //Der Suchtext kommt mind. 1x vor
   begin
    Result := True;

    A_String := ReadBinFile(FName);  //Ganze Datei einlesen - A_String ist ein AnsiString
                                     //ReadBinFile wandelt dabei auch #0 in Leerzeichen um

      //Nun ermitteln, wie oft der Suchtext im String A_String vorkommt
    Found := CountPos(AnsiLowerCase(LabeledEdit2Text), AnsiLowerCase(A_String))

    break;  //Weiterlesen ist hier unnötig
   end;

  until (NumRead = 0);

  CloseFile(F);

 except
  ShowMessage('Zugriff verweigert: '+FName);
  Result := True;
 end;


Hier die Funktion CountPos, die ermittelt, wie oft ein String in einem anderen enthalten ist. (StrReplace tut das selbe wie StringReplace, nur mit AnsiString und LongInt statt String und Integer):

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
{CountPos wurde im Original von Stefan Kellermann geschrieben und wurde nur angepasst}
function TForm1.CountPos(const subtext: AnsiString; Text: AnsiString): LongInt;
begin
 if (Length(subtext) = 0or (Length(Text) = 0or (Pos(subtext, Text) = 0then
  Result := 0
 else
  Result := (Length(Text) - Length(StrReplace(Text, subtext, '', [rfReplaceAll]))) div
   Length(subtext);
end;

Nun tritt das Phänomen auf, dass CountPos fast immer die korrekte Anzahl ermittelt, manchmal, bei mehreren hundert Vorkommen des Suchtextes jedoch um 1 bis 2 Stellen weniger errechnet als mit einem anderen Datei-Suchprogramm (von Norton, also zuverlässig!). Offenbar ist es auch nicht egal, mit welchem Wert man das Array Buf angibt:

Delphi-Quelltext
1:
2:
3:
4:
Buf: array [0..8192of Char;
//oder
Buf: array [0..1024of Char;
//oder sonstwas kann sich unter Umständen auf CountPos auswirken

Meine Fragen nun: Ist BlockRead wirklich ungenau, oder wie kann man auf bessere und schnellere Weise ermitteln, wie oft ein String in einem anderen enthalten ist?

Wo könnte der Fehler liegen? Für Hilfe wäre ich jedenfalls sehr dankbar!

//Edit: Nur der Ordnung halber:
Hier der richtige Code - bei Buf muss -1 angegeben werden, da es mit 0 beginnt, während S als String mit 1 beginnt:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
  S := '';

  repeat
   Buf := '';

   BlockRead(SourceFile, Buf, Sizeof(Buf), NumRead);
   SetLength(S, NumRead);

   for I := 1 to NumRead do
   begin
    if (Char(Buf[I-1]) = #0then S[I] := ' '
    else                          S[I] := Buf[I-1];
   end;

   AString := AString+S;

  until (NumRead = 0);


jasocul - Fr 04.02.05 21:59

Vielleicht habe ich es überlesen, aber bedenkst du den Sonderfall, wenn der Suchbegriff da ist, aber gerade über die Buffer-Grenze geht? Wenn also nur ein Teil des Strings im Buffer ist?


galagher - Fr 04.02.05 22:17

jasocul hat folgendes geschrieben:
Vielleicht habe ich es überlesen, aber bedenkst du den Sonderfall, wenn der Suchbegriff da ist, aber gerade über die Buffer-Grenze geht? Wenn also nur ein Teil des Strings im Buffer ist?

Zuerst mal danke für die Antwort!

Wenn das wirklich so ist, was kann ich da machen? Ich denke doch, BlockRead liest in den Buffer, und zwar maximal SizeOf(Buffer), oder, wenn der noch ungelesene Teil kleiner ist, dann eben weniger. Oder nicht? Denn ich habe herausgefunden, dass der Suchbegiff tatsächlich da ist, nur eben nicht "entdeckt" wird!

Kennt jemand eine Lösung?


grayfox - Fr 04.02.05 23:24

hallo galagher!

ich versuche dir mit einem beispiel zu verdeutlichen, was dir jacosul schon mit worten zu erklären versucht hat:

das auslesen einer datei mittels blockread ist für deinen anwendungsfall nicht geeignet, da folgenes auftreten kann

beispiel: du suchst in deinem text nach 'Abendessen'

- du hat eine blockgröße von zb 8096 bytes definiert - somit werden 8096 bytes in den buffer gelesen
- in deinem buffer steht nun ' xxxxxxxx Abende' und dann sind die 8096 bytes voll
- somit ist das wort 'Abendessen' nicht vollständig und wird auch nicht als solches erkannt
- der nächste block beginnt mit 'ssen xxxxxxxxxxx' - dort wird der suchbegriff daher auch nicht gefunden.

verstanden? na hoffentlich! ;)

abhilfe schafft bereits ein zeilenweises auslesen der textdatei mit 'readln', oder wenn die texte nicht zu lang sind, ein einlesen in eine stringlist.
somit bleiben schon mal die wörter zusammen und werden nicht abgeschnitten.
probleme kann es natürlich noch geben, wenn die wörter am zeilenende abgeteilt sind...


uall@ogc - Fr 04.02.05 23:26

1. wenn du ein array machst bitte in 2er potenzen also von 0..8192-1
2. kann es sein das der string zerschnitten wird (1. block 'hehehehal' 2. block 'lohehehehe' wenn z.b. nach hallo suchst (wie jasocul gesagt)
3. würde cih dir tfilestream empfehlen, oder wenns mit blockread machen willst musst du mittels
seek an die jetzige position- länge des textes der gesucht werden soll springen, dann erst nächsten block und das wiederholen

text = 'aaaa'
x: integer = 1;


leseblock (8192 bytes) <-|
inc(x); |
springe mit seek an stelle (x*8192-length(text)) -|

das musst du bei tfilstream auch machen aber da brauchste nicht so auf excpetions achten :>
würde ich mir ne eigene pos funktion schreiben und alles im array of byte machen, das was eingelesen wird sowie der string der geuscht werden soll


galagher - Sa 05.02.05 13:48

grayfox hat folgendes geschrieben:
- du hat eine blockgröße von zb 8096 bytes definiert - somit werden 8096 bytes in den buffer gelesen
- in deinem buffer steht nun ' xxxxxxxx Abende' und dann sind die 8096 bytes voll
- somit ist das wort 'Abendessen' nicht vollständig und wird auch nicht als solches erkannt

Danke, klar, so kann das nicht immer klappen! Aber ich lese die Datei als Ganzes in eine AnsiString-Variable ein und suche erst dann im gesamten String (=Dateiinhalt). Dazu habe ich mir (mit Hilfe aus dem Internet und durch Lesen im Delphi-Forum) eine Prozedur ReadFromBinFile geschrieben, die mir #0 in Leerzeichen umwandelt. Das klappt sogar sehr schnell.

Was ich aber nicht kapiere:

Delphi-Quelltext
1:
2:
3:
Buf: array [0..8191of Char;  //dann klappt's
Buf: array [0..4096of Char;  //dann klappt's
Buf: array [0..8192of Char;  //dann klappt's nicht

Ich lese IMMER den GANZEN Dateiinhalt und suche erst dann. Es kalppt auch, wenn ich in ein RichEdit einlese und dann in RichEdit1.Text suche. Seltsam, oder?


uall@ogc - Sa 05.02.05 13:50

es sollte bei allen 3 klappen aber nimm halt besser ne 2er potzen, (also gewöhn dir das an :P)
ich schreib dir gleich mal funktion die das kann und denk ich mal nen bischen schneller ist :>


uall@ogc - Sa 05.02.05 14:07


Delphi-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:
function findinfile(fn,text: string): boolean;
  procedure lowercasep(p: pointer; size: integer);
  var i: integer;
      b: ^byte;
  begin
    for i := 1 to size do
    begin
      b := pointer(integer(p)+i-1);
      if ((b^ >= ord('A')) and (b^ <= ord('Z'))) then
        b^ := b^-(ord('A') - ord('a'));
    end;
  end;

  function searchin(p: pointer; text: string; bsize: integer): boolean;
  var i,j,k: integer;
    b: ^byte;
  begin
    result := false;
    text := lowercase(text);
    lowercasep(p,bsize);
    for i := 1 to bsize-length(text) do
    begin
      k := 0;
      for j := 1 to length(text) do
      begin
        b := pointer(integer(p)+i+j-2);
        if b^ = byte(text[j]) then inc(k);
      end;
      if (k = length(text)) then
      begin
        result := true;
        exit;
      end;
    end;
  end;

const bufsize = 4096;
var p: pointer;
    fm: tfilestream;
    sf, read: integer;
begin
  result := false;
  if fileexists(fn) then
  begin
    getmem(p,bufsize);
    fm := tfilestream.create(fn,fmopenread);
    sf := fm.Size;
    while sf > 0 do
    begin
      read := fm.Read(p^,bufsize);
      if searchin(p,text,read) then
      begin
        result := true;
        exit;
      end;
      sf := sf-bufsize-length(text);
      fm.Seek(-length(text),1)
    end;
    fm.free;
    freemem(p);
  end;
end;


ungetestet, hab ich blind geschrieben


I.MacLeod - Sa 05.02.05 14:44


Delphi-Quelltext
1:
2:
3:
4:
5:
   for I := 1 to Length(Buf) do
   begin
    if (Char(Buf[I]) = #0then S[I] := ' '
    else                        S[I] := Buf[I];
   end;


imho müsste da statt Length(Buf) NumRead stehen.


uall@ogc - Sa 05.02.05 14:52

das ist zu vernachlässigen (wäre zwar bisl schneller aber egal) da length(buf) immer >= ist wie numread


I.MacLeod - Sa 05.02.05 14:57

Wirklich? Wenn die Datei 12000 Bytes groß ist, ist Numread beim ersten Mal 8192 und beim zweiten Mal 3808. Wenn aber z.B. an Byte 4000 der ersten Runde der Suchstring vorkam, wird der so doch jetzt nochmal mit eingelesen, oder nicht? (Zusätzlich zu dem was oben schon genannt wurde)
...und die Länge vom String müsste auch auf Numread gesetzt werden.


uall@ogc - Sa 05.02.05 15:00

1. es wird nur geschaut ob der string da ist, wenn der gefunden wurde dann ist sofort ende
2. blöd isses nur bei dateien die < 8192 sind weil dann ist der speicher eventl nicht initialisert
3. funzt die methode ja glaub ich nicht
4. hab ich ja eine neue geschrieben die gehen sollte


galagher - Sa 05.02.05 15:01

uall@ogc hat folgendes geschrieben:
es sollte bei allen 3 klappen aber nimm halt besser ne 2er potzen

Kannst du mir ein praktisches Beispiel dazu geben, wie ich das mache?
uall@ogc hat folgendes geschrieben:
ich schreib dir gleich mal funktion die das kann und denk ich mal nen bischen schneller ist :>

Ich vertehe es nicht wirklich und es will mir auch nicht gelingen, den Zähler einzubauen, der die Anzahl der Vorkommen des Suchtextes zählt! Der Code ist korrekt, findet aber auch Dateien ohne den Suchtext!

Aber das mit den 2er Potenzen - kannst du mir da ein Beispiel geben? Danke!


uall@ogc - Sa 05.02.05 15:05

das mit den zweierpotenzen ist nur ne schönheitssache

z.b. var bub: array[0..8192-1] of byte und nicht array[0..8192] da du dann 8193 bytes hättest

2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192

sorry ich weiß nicht was an deinem code falsch ist aber vill kommst ja mit meinem code weiter...


galagher - Sa 05.02.05 15:09

uall@ogc hat folgendes geschrieben:
3. funzt die methode ja glaub ich nicht

Es geht, wenn man:
1. Entweder mit einer Textkomponente (Memo etc.) arbeitet und dann in zB. Memo1.Text sucht (ist aber wenig professionell, zumal man das Memo nur dazu braucht, nicht, um Text darzustellen).

2. Wenn man etwa "Buf: array [0..8191] of Char;" angibt - in diesem Zusammenhang verstehe ich das mit den 2er-Potenzen nicht!


I.MacLeod - Sa 05.02.05 15:12

Also...

1. Du willst die Anzahl der Vorkommen eines Suchstrings in einer Datei ermitteln
2. ReadBinFile liest die ganze Datei ein
3. CountPos gibt an, wie oft der erste Parameter im zweiten Vorkommt.

Stimmt das alles? Dann frag ich mich, warum du es nicht einfach so machst:


Delphi-Quelltext
1:
Found := CountPos(AnsiLowerCase(LabeledEdit2Text), AnsiLowerCase(ReadBinFile(FName)))                    


Du liest ja so oder so die gesamte Datei ein. Hiernach müsste jedenfalls in Found die Anzahl drinstehen.


uall@ogc - Sa 05.02.05 15:15

1. vergiss das mit der 2er potenz einfach
2. memo kannste kniggn (wegen #0)
3. große dateien ganz einlesen ist auch fürn *popo*


galagher - Sa 05.02.05 15:16

uall@ogc hat folgendes geschrieben:
z.b. var bub: array[0..8192-1] of byte und nicht array[0..8192] da du dann 8193 bytes hättest

Da kann ich ja gleich 8191 angeben, denn nach Adam Riese: 8192-1 = 8191! Jedenfalls: mit 8192-1 klappt es. Ich habe auch NumRead anstelle von Length(Buf) angegeben - je schneller, desto besser!
Dein Code funktioniert - hab' ihn vorher nur falsch eingesetzt! Hut ab - du zauberst sowas in 17 Minuten aus dem Hut, testest es nicht mal und es haut hin! Danke!


F34r0fTh3D4rk - Sa 05.02.05 15:16

meinst du dass #0 net geht ? bei mir ist das bei der listbox so, alles nach einem #0 wird nicht mehr angezeigt


uall@ogc - Sa 05.02.05 15:28

hab einen fehler im quelltext gefunden -> (b^ < ord('Z')) muss zu (b^ <= ord('Z')) gemacht werden


galagher - Sa 05.02.05 15:57

I.MacLeod hat folgendes geschrieben:

1. Du willst die Anzahl der Vorkommen eines Suchstrings in einer Datei ermitteln
2. ReadBinFile liest die ganze Datei ein
3. CountPos gibt an, wie oft der erste Parameter im zweiten Vorkommt.

1., 2., 3.: Ja.
Dein Vorschlag ist besser und funktioniert! Hast recht - danke!

Danke euch allen erstmal für eure rasche Hilfe! Habt mir echt geholfen - so komme ich weiter, es ist schneller und ermittelt korrekte Werte! :D