将文件的一部分复制到流中



全局目标是使用文件的一部分来获取校验和以查找重复的电影和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);

相关内容

  • 没有找到相关文章

最新更新