Delphi fast file copy

后端 未结 6 1949
小鲜肉
小鲜肉 2020-12-12 18:17

I\'m writing an app that supposed to copy a bunch of files from one place to another. When I\'m using TFileStream for the copy it is 3-4 times slower than copying the files

6条回答
  •  悲哀的现实
    2020-12-12 18:55

    There are a few options.

    1. You could call CopyFile which uses the CopyFileA windows API
      • You could call the api which explorer uses (the windows api SHFileOperation). An example of calling that function can be found on SCIP.be
      • You could write your own function which uses a buffer.

    If you know the kind of files your going to copy, the 3th method will normally outperform the others. Because the windows API's are more tuned for overall best case (small files, large files, files over network, files on slow drives). You can tune your own copy function more to fit your needs.

    Below is my own buffered copy function (i've stripped out the GUI callbacks):

    procedure CustomFileCopy(const ASourceFileName, ADestinationFileName: TFileName);
    const
      BufferSize = 1024; // 1KB blocks, change this to tune your speed
    var
      Buffer : array of Byte;
      ASourceFile, ADestinationFile: THandle;
      FileSize: DWORD;
      BytesRead, BytesWritten, BytesWritten2: DWORD;
    begin
      SetLength(Buffer, BufferSize);
      ASourceFile := OpenLongFileName(ASourceFileName, 0);
      if ASourceFile <> 0 then
      try
        FileSize := FileSeek(ASourceFile, 0, FILE_END);
        FileSeek(ASourceFile, 0, FILE_BEGIN);
        ADestinationFile :=  CreateLongFileName(ADestinationFileName, FILE_SHARE_READ);
        if ADestinationFile <> 0 then
        try
          while (FileSize - FileSeek(ASourceFile, 0, FILE_CURRENT)) >= BufferSize do
          begin
            if (not ReadFile(ASourceFile, Buffer[0], BufferSize, BytesRead, nil)) and (BytesRead = 0) then
             Continue;
            WriteFile(ADestinationFile, Buffer[0], BytesRead, BytesWritten, nil);
            if BytesWritten < BytesRead then
            begin
              WriteFile(ADestinationFile, Buffer[BytesWritten], BytesRead - BytesWritten, BytesWritten2, nil);
              if (BytesWritten2 + BytesWritten) < BytesRead then
                RaiseLastOSError;
            end;
          end;
          if FileSeek(ASourceFile, 0, FILE_CURRENT)  < FileSize then
          begin
            if (not ReadFile(ASourceFile, Buffer[0], FileSize - FileSeek(ASourceFile, 0, FILE_CURRENT), BytesRead, nil)) and (BytesRead = 0) then
             ReadFile(ASourceFile, Buffer[0], FileSize - FileSeek(ASourceFile, 0, FILE_CURRENT), BytesRead, nil);
            WriteFile(ADestinationFile, Buffer[0], BytesRead, BytesWritten, nil);
            if BytesWritten < BytesRead then
            begin
              WriteFile(ADestinationFile, Buffer[BytesWritten], BytesRead - BytesWritten, BytesWritten2, nil);
              if (BytesWritten2 + BytesWritten) < BytesRead then
                RaiseLastOSError;
            end;
          end;
        finally
          CloseHandle(ADestinationFile);
        end;
      finally
        CloseHandle(ASourceFile);
      end;
    end;
    

    Own functions:

    function OpenLongFileName(const ALongFileName: String; SharingMode: DWORD): THandle; overload;
    begin
      if CompareMem(@(ALongFileName[1]), @('\\'[1]), 2) then
        { Allready an UNC path }
        Result := CreateFileW(PWideChar(WideString(ALongFileName)), GENERIC_READ, SharingMode, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0)
      else
        Result := CreateFileW(PWideChar(WideString('\\?\' + ALongFileName)), GENERIC_READ, SharingMode, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
    end;
    function OpenLongFileName(const ALongFileName: WideString; SharingMode: DWORD): THandle;  overload;
    begin
      if CompareMem(@(WideCharToString(PWideChar(ALongFileName))[1]), @('\\'[1]), 2) then
        { Allready an UNC path }
        Result := CreateFileW(PWideChar(ALongFileName), GENERIC_READ, SharingMode, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0)
      else
        Result := CreateFileW(PWideChar('\\?\' + ALongFileName), GENERIC_READ, SharingMode, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
    end;
    
    function CreateLongFileName(const ALongFileName: String; SharingMode: DWORD): THandle; overload;
    begin
      if CompareMem(@(ALongFileName[1]), @('\\'[1]), 2) then
        { Allready an UNC path }
        Result := CreateFileW(PWideChar(WideString(ALongFileName)), GENERIC_WRITE, SharingMode, nil, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0)
      else
        Result := CreateFileW(PWideChar(WideString('\\?\' + ALongFileName)), GENERIC_WRITE, SharingMode, nil, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
    end;
    function CreateLongFileName(const ALongFileName: WideString; SharingMode: DWORD): THandle; overload;
    begin
      if CompareMem(@(WideCharToString(PWideChar(ALongFileName))[1]), @('\\'[1]), 2) then
        { Allready an UNC path }
        Result := CreateFileW(PWideChar(ALongFileName), GENERIC_WRITE, SharingMode, nil, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0)
      else
        Result := CreateFileW(PWideChar('\\?\' + ALongFileName), GENERIC_WRITE, SharingMode, nil, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
    end;
    

    The code is a bit longer that necessary, because I included a retry mechanism to support a wifi connection problem I had.

    So this part

        if BytesWritten < BytesRead then
        begin
          WriteFile(ADestinationFile, Buffer[BytesWritten], BytesRead - BytesWritten, BytesWritten2, nil);
          if (BytesWritten2 + BytesWritten) < BytesRead then
            RaiseLastOSError;
        end;
    

    could be written as

        if BytesWritten < BytesRead then
        begin
            RaiseLastOSError;
        end;
    

提交回复
热议问题