全局目标是使用文件的一部分来获取校验和以查找重复的电影和MP3文件,为此,我必须获取文件的一部分并生成 MD5,因为在某些情况下整个文件大小高达 25 Gigs,如果我发现重复项,那么我将执行完整的 MD5 以避免任何错误文件删除的错误我没有任何问题,我从流生成 MD5 ,它将使用 Indy 组件完成所以对于第一部分我必须复制文件的前 1MB
所以我做了这个函数
但是所有检查的内存流都是空的!
function splitFile(FileName: string): TMemoryStream;
var
fs: TFileStream;
ms : TMemoryStream;
begin
fs := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite) ;
ms := TMemoryStream.Create;
fs.Position :=0;
ms.CopyFrom(fs, 1048576);
result := ms;
end;
我该如何解决这个问题?或者我的问题在哪里?
更新1 - (脏测试) :
此代码返回错误stream read error
memo2 也显示一些字符串,但 memo3 为空!!
function splitFile(FileName: string): TMemoryStream;
var
fs: TFileStream;
ms : TMemoryStream;
begin
fs := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite) ;
ms := TMemoryStream.Create;
fs.Position :=0;
form1.Memo2.Lines.LoadFromStream(fs);
ms.CopyFrom(fs,1048576);
ms.Position := 0;
form1.Memo3.Lines.LoadFromStream(ms);
result := ms;
end;
完整代码
function splitFile(FileName: string): TMemoryStream;
var
fs: TFileStream;
ms : TMemoryStream;
i,BytesToRead : integer;
begin
fs := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
ms := TMemoryStream.Create;
fs.Position :=0;
BytesToRead := Min(fs.Size-fs.Position, 1024*1024);
ms.CopyFrom(fs, BytesToRead);
result := ms;
// fs.Free;
// ms.Free;
end;
function streamFile(FileName: string): TFileStream;
var
fs: TFileStream;
ms : TMemoryStream;
begin
fs := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite) ;
result := fs;
end;
function GetFileMD5(const Stream: TStream): String; overload;
var MD5: TIdHashMessageDigest5;
begin
MD5 := TIdHashMessageDigest5.Create;
try
Result := MD5.HashStreamAsHex(Stream);
finally
MD5.Free;
end;
end;
function getMd5HashString(value: string): string;
var
hashMessageDigest5 : TIdHashMessageDigest5;
begin
hashMessageDigest5 := nil;
try
hashMessageDigest5 := TIdHashMessageDigest5.Create;
Result := IdGlobal.IndyLowerCase ( hashMessageDigest5.HashStringAsHex ( value ) );
finally
hashMessageDigest5.Free;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
Path,hash : String;
SR : TSearchRec;
begin
if od1.Execute then
begin
Path:=ExtractFileDir(od1.FileName); //Get the path of the selected file
DirList:=TStringList.Create;
try
if FindFirst(Path+'*.*', faArchive , SR) = 0 then
begin
repeat
if (SR.Size>10240) then
begin
hash := GetFileMD5(splitFile(Path+''+SR.Name));
end
else
begin
hash := GetFileMD5(streamFile(Path+''+SR.Name));
end;
memo1.Lines.Add(hash+' | '+SR.Name +' | '+inttostr(SR.Size));
application.ProcessMessages;
until FindNext(SR) <> 0;
FindClose(SR);
end;
finally
DirList.Free;
end;
end;
end;
输出:
D41D8CD98F00B204E9800998ECF8427E | eslahat.docx | 13338
D41D8CD98F00B204E9800998ECF8427E | EXT-3000-Data-Sheet.pdf | 682242
D41D8CD98F00B204E9800998ECF8427E | faktor khate ekhtesasi firoozpoor.pdf | 50091
D41D8CD98F00B204E9800998ECF8427E | FileZilla_3.9.0.5_win32-setup.exe | 6057862
D41D8CD98F00B204E9800998ECF8427E | FileZilla_3.9.0.6_win32-setup.exe | 6126536
11210486C9E54E12DA9DF687792257EA | get_stats_of_all_members_of_mu(1).php | 6227
11210486C9E54E12DA9DF687792257EA | get_stats_of_all_members_of_mu.php | 6227
D41D8CD98F00B204E9800998ECF8427E | GOMAUDIOGLOBALSETUP.EXE | 6855616
D41D8CD98F00B204E9800998ECF8427E | harvester-master(1).zip | 54255
D41D8CD98F00B204E9800998ECF8427E | harvester-master.zip | 54180
这是我快速为您编写的一个过程,它将使您能够将文件(块)的一部分读取到内存流中。
我之所以将其变成一个过程而不是函数,是因为可以为不同的块重用相同的内存流。通过这种方式,您可以避免所有这些内存分配/取消分配,并减少引入内存泄漏的机会。
为了能够做到这一点,您需要将过程的内存流句柄作为变量参数。
我还增加了两个参数。一个用于指定块大小(要从文件中读取的数据量)和块编号。
我还做了一些基本的保护措施,告诉你什么时候你想读取一个超出文件范围的块。并且能够自动减小最后一个块的大小,因为并非所有文件大小都是 oyur 块大小的倍数(在您的情况下,并非所有文件的大小都恰好是 X 兆字节,其中 X 是任何有效整数)。
procedure readFileChunk(FileName: string; var MS: TMemoryStream; ChunkNo: Integer; ChunkSize: Int64);
var fs: TFileStream;
begin
fs := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
if ChunkSize * (ChunkNo-1) <= fs.Size then
begin
fs.Position := ChunkSize * (ChunkNo-1);
if fs.Position + ChunkSize <= fs.Size then
ms.CopyFrom(fs, ChunkSize)
else
ms.CopyFrom(fs, fs.Size - fs.Position);
end
else
MessageBox(Form2.WindowHandle, 'File does not have so many chunks', 'WARNING!', MB_OK);
fs.Free;
end;
您可以通过调用以下命令来使用此过程:
readFileChunk(FileName,MemoryStream,ChunkNumber,ChunkSize);
在调用此过程之前,请确保已创建内存流。
此外,如果您想多次重用相同的内存流,请不要忘记在调用此过程之前将其位置设置为 0,否则新数据将被添加到流的末尾,从而不断增加内存流大小。
更新:
经过一些试验,我发现问题出在您的GetFileMD5方法中。
我无法确切解释为什么会发生这种情况,但是如果您将 TMemoryStream 传递给 TStream 参数,TStream 参数只是接受它,因此 MD5 哈希算法将其视为空句柄。
当我去将参数类型更改为 TMemoryStream 时,代码有效,但您不再能够将 TFileStream 传递给 GetFileMD5 方法,因此它破坏了以前工作的整个文件的哈希生成。
溶液:
因此,在进行了更多的挖掘之后,我有一个好消息要告诉你。
您甚至不需要使用 TMemoryStreams。"HashStreamAsHex"函数可以接受两个可选参数,这些参数允许您定义数据的起点和要从中生成MD5哈希字符串的数据块的大小。这也适用于TFileStream。
因此,为了从文件的一小部分生成MD5哈希字符串,请调用以下命令:
MD5.HashStreamAsHex(Stream,StartPosition,DataSize);
StartPositon 指定哈希操作流中的初始偏移量。当 StartPosition 包含正非零值时,流位置将在计算哈希值之前移动到指定的偏移量。当 StartPosition 包含值 -1 时,流的当前位置将用作指定流的初始偏移量。
DataSize 指示要包含在哈希操作中的流中的字节数。当 DataSize 包含负值 (<0) 时,当前流位置剩余的字节将用于哈希操作。否则,将使用数据大小中的字节数。如果 DataSize 大于流的大小,则两个值中较小的一个用于操作。
在您从第一个兆字节获取 MD5 哈希的情况下,您将调用:
MD5.HashStreamAsHex(Stream,0,1024*1024);
现在我相信您可以修改其余代码以使其根据需要工作。如果没有,请告诉它在哪里停止,我会帮助你。
我假设您的代码不会引发异常。如果是这样,你肯定会提到这一点。我还假设该文件足够大,可以尝试读取。
您的代码确实会复制。如果对CopyFrom
的调用未引发异常,则内存流包含文件的前 1024000
个字节。
但是,在调用 CopyFrom
之后,内存流的指针位于流的末尾,因此如果您从中读取,您将无法读取任何内容。也许您需要将流指针移动到开头:
ms.Position := 0;
然后从内存流中读取。
1MB = 1024*1024,FWIW。
更新
可能我上面的假设是不正确的。您的代码似乎可能会引发异常,因为您尝试读取文件末尾以外的内容。
您真正想要做的是尽可能多地阅读文件的第一部分。这是两行。
BytesToRead := Min(Source.Size-Source.Position, 1024*1024);
Dest.CopyFrom(Source, BytesToRead);