Entwickler-Ecke

Dateizugriff - Datei Byte weise auslesen


anno2007 - Sa 22.01.11 20:03
Titel: Datei Byte weise auslesen
Hey,
ich muss für einen MD5 Dateivergleich eine Datei auslesen.
Allerdings nehmen wir nur die ersten 3145728 und die letzens 3145728 Bytes der Datei zum auslesen.

In PHP ist das recht flott.
In Delphi allerdings leese ich bis jetzt die ganze Datei in einen String und teile den dann auf:


Delphi-Quelltext
1:
2:
3:
  filestr := FileToString('C:\Users\****\Desktop\testdatei.exe');
  str1 := Copy(filestr, 13145728);
  str2 := Copy(filestr, length(filestr)-3145728+1, length(filestr));



Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
function FileToString(const AFileName: string): AnsiString;
var
  f: TFileStream;
  l: Integer;
begin
  Result := '';
  f := TFileStream.Create(AFileName, fmOpenRead or fmShareDenyWrite);
  try
    l := f.Size;
    if L > 0 then
    begin
      SetLength(Result, l);
      F.ReadBuffer(Result[1], l);
    end;
  finally
    F.Free;
  end;
end;


Hat da jemand eine schnellere Lösung?
Weil das ist wirklich extrem langsam...

Thx
anno2007


Moderiert von user profile iconNarses: Topic aus Delphi Language (Object-Pascal) / CLX verschoben am So 23.01.2011 um 12:41


jaenicke - Sa 22.01.11 20:20

Ich habe da mal was gemacht:
http://www.delphi-forum.de/viewtopic.php?t=99933
Weiter unten ist auch eine Stream-Implementierung verlinkt.

Damit komme ich bei zeichenweisem Auslesen auf meinem aktuell schnellsten PC auf knapp 100 MiB/s.
// EDIT:
Und von der SSD gehts noch schneller. Letztlich ist hier die sequentielle Festplattengeschwindigkeit der limitierende Faktor, nicht mehr die bei vielen kleineren Zugriffen.


Delete - Sa 22.01.11 20:21

Du willst Bytes auslesen und nimmst als Datentyp eine Zeichenkette? Das ist Unsinn. Nimm ein Byte Array.


anno2007 - Sa 22.01.11 20:32

Ja, ok ich bruache schon Strings um die der MD5 Funktion zu übergeben.

@jaenicke, das sieht gut aus, Danke :)


jaenicke - Sa 22.01.11 20:51

Nebenbei: Da du eh nur zwei Blöcke auslesen willst, könntest du das auch direkt mit TFileStream machen. Denn du brauchst ja auch damit nicht die ganze Datei auslesen. Schließlich kannst du die Position setzen und dann die gewünschte Anzahl Byte auslesen.
Deine beiden Strings kannst du vorher ja auch auf die entsprechende Länge setzen.

Denn was bei dir so lange dauert ist ja danach das Umkopieren des unnötig großen Strings.


elundril - Sa 22.01.11 20:55

user profile iconanno2007 hat folgendes geschrieben Zum zitierten Posting springen:
Ja, ok ich bruache schon Strings um die der MD5 Funktion zu übergeben.


Aha, warum? MD5 sollte doch eigentlich Byteweise funktionieren und der String wird in der MD5-Funktion sicher umgewandelt, oder? Kannst du demnach die Funktion einfach umbauen?

lg elundril


jaenicke - Sa 22.01.11 21:16

Mit der Implementierung in den Indy Komponenten geht das z.B. auch extrem schnell, indem man die Datei mit user profile iconFlamefires Stream-Implementierung lädt und den Stream direkt an die MD5 Funktion füttert.

Damit sollte von der ganzen Datei sehr schnell der Hash erstellt werden.


anno2007 - Di 25.01.11 02:27

Danke für die Antworten :).
Ich konnte leider nicht früher antworten...

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Nebenbei: Da du eh nur zwei Blöcke auslesen willst, könntest du das auch direkt mit TFileStream machen. Denn du brauchst ja auch damit nicht die ganze Datei auslesen. Schließlich kannst du die Position setzen und dann die gewünschte Anzahl Byte auslesen.
Deine beiden Strings kannst du vorher ja auch auf die entsprechende Länge setzen.

Denn was bei dir so lange dauert ist ja danach das Umkopieren des unnötig großen Strings.


Ja, das hab ich mir auch gedacht.
Kann ich dann einfach TFileStream.Create und dann mit .read auslesen?
Wie setze ich dann den Zeiger ab wo ich auslesen will auf 3MB vor dem Dateiende?

Würde mich über eine weiter Antwort sehr freuen :).

Vielen Dank
anno07


jaenicke - Di 25.01.11 06:33

user profile iconanno2007 hat folgendes geschrieben Zum zitierten Posting springen:
Kann ich dann einfach TFileStream.Create und dann mit .read auslesen?
Ja, wie jetzt auch.

user profile iconanno2007 hat folgendes geschrieben Zum zitierten Posting springen:
Wie setze ich dann den Zeiger ab wo ich auslesen will auf 3MB vor dem Dateiende?
Naja, ganz einfach...

Delphi-Quelltext
1:
MyStream.Position := MyStream.Size - x;                    
Wobei bei dir die Prüfung fehlt, ob die Datei überhaupt so groß ist.


anno2007 - Di 25.01.11 17:59

Hallo,
das habe ich jetzt so umgesetzt:

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:
function FileToMd5(const AFileName: string): AnsiString;
var res1, res2 : Ansistring;
    f : TFileStream;
    size, nsize : integer;
begin
  res1 := '';
  res2 := '';

  f := TFileStream.Create(AFileName, fmOpenRead or fmShareDenyWrite);
  try
    size := f.Size;
    if (size>0then
    begin
      nsize := size;

      if (nsize > 3145728then
        nsize := 3145728;

      SetLength(res1, nsize);
      SetLength(res2, nsize);

      F.ReadBuffer(res1[1], nsize);

      F.Position := size-nsize;
      F.ReadBuffer(res2[1], nsize);
    end;
  finally
    F.Free;
  end;

  result := md5string(res1 + res2 + inttostr(size));
end;


Funktioniert einwandfrei, nur ist das ganze leider nicht schneller gewordern. Ich vermute es liegt an der langsamen MD5 Funktion:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
function MD5String(const Input:string):string;
var
    MD5: IMD5;
begin
  MD5 := GetMD5;
  MD5.Init;
  MD5.Update(TByteDynArray(RawByteString(Input)), Length(RawByteString(Input)));
  result := LowerCase(MD5.AsString);
end;


Aber ein versuch war es Wert! :)

Danke!


jaenicke - Di 25.01.11 18:10

Also selbst für eine komplette 65 MiB Datei braucht es bei mir gerade einmal 1-2 Sekunden zur Erstellung des Hashes... (mit Indy [http://delphi.about.com/od/objectpascalide/a/delphi-md5-hash.htm], wobei du bei Delphi XE "HashStreamAsHex" statt "AsHex" + "HashValue" benutzen musst)


anno2007 - Di 25.01.11 18:43

Ok, ich hab jetzt wieder die Fuktion:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
function MD5String(const Input:string):string;
var
  IDMD5:TIdHashMessageDigest5;
begin
  IDMD5:=TIdHashMessageDigest5.Create;
  try
    Result:=LowerCase(IDMD5.HashStringAsHex(Input));
  finally
    IDMD5.Free;
  end;
end;


Es sieht so aus als wäre die ein gutes Stück schneller, nur leider wieder dieses Problem:
http://www.delphi-forum.de/viewtopic.php?t=103363

Kannst du mir sagen wie man die in AnsiString umschreibt?
Ich habe leider keine Ahnung wo ich das suchen muss.

Ganze Dateien zu hashen dauert zu lange. Ich bin halt dabei ein Update-System zu schreiben und dann nehme ich halt von jeder Datei die ersten und die letzen 3 MB und ihre Dateigröße. Das geht auch in PHP passabel schnell:
http://91.203.145.37/~elementm/patch.php


elundril - Di 25.01.11 18:58

ich bin mal ganz verrückt und sag einfach mal: ich hab keine ahnung aber versuch doch mal das hier:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
function MD5String(const Input:string):string;
var
  IDMD5:TIdHashMessageDigest5;
begin
  IDMD5:=TIdHashMessageDigest5.Create;
  try
    Result:=LowerCase(IDMD5.HashStringAsHex(AnsiString(Input)));
  finally
    IDMD5.Free;
  end;
end;


lg elundril


anno2007 - Di 25.01.11 19:19

So gehts:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
function MD5String(const Input:string):string;
var
  IDMD5:TIdHashMessageDigest5;
begin
  IDMD5:=TIdHashMessageDigest5.Create;
  try
    Result:=LowerCase(IDMD5.HashStringAsHex(Input, TEncoding.Default));
  finally
    IDMD5.Free;
  end;
end;


In dem Thread hab ich das gefunden:
https://forums.embarcadero.com/thread.jspa?threadID=25766

/Jaaaaaa, jetzt geht es auch ums 2-fache schneller.
Vielen Dank an alle, besonder an jaenicke ;).


Flamefire - Mi 26.01.11 10:43

Trotzdem wandelst du einen Stream in einen String um, um den dann zu hashen. Es gibt doch, wie schon erwähnt, HashStreamAsHex statt HashStringAsHex.
Dann hast du keinen solchen fehleranfälligen Code. Solltest also deine Berechnung wirklich auf Bytes statt auf "Zeichen" umstellen. Besonders da deine Datei ja keine Zeichen (oder wenn dann rein zufällig) enthällt.
Mit TFastFilestream (von mir) kannst du sehr schnell Dateiene einlesen. Das ganze in einen MemoryStream und dann so in die MD5 funktion. Ist das sauberste.


anno2007 - Mi 26.01.11 11:48

Hey,
ok, das wird dann der nächste Schritt sein.
Werds mir anschauen,
Thx :)


kwhk - Sa 29.01.11 19:36

Ich habe nur ein paar Anmerkungen...
1.) Die Variablen für Dateilänge usw. müssen int64 statt integer (4 byte) sein, sonst gibt es Probleme mit Dateien, die länger als 2,2 GB sind. TFileStream.Size liefert die korrekte Länge als int64.
2.) Wenn die ersten und die letzten 3 MB der Datei gelesen werden sollen, muss festgelegt werden, was zu tun ist wenn...
2.1 die Datei kürzer als 3 MB ist : s1: komplette Datei s2: Leerstring
2.2 die Datei kürzer als 6 MB ist : s1: 3 MB s2: Rest der Datei