2017-01-05 57 views
2

在磁盤之間複製大文件的工具中,我用System.IO.Stream.CopyToAsync替換了System.IO.FileInfo.CopyTo方法中的 。 這允許在拷貝期間更快的拷貝和更好的控制,例如,我可以停止複製。 但是這會造成複製文件的更多碎片化。當我複製數百兆字節的文件時尤其令人討厭。C#I/O異步(copyAsync):如何避免文件碎片?

如何在複製過程中避免磁盤碎片?

使用xcopy命令,/ j開關複製文件而不緩衝。它被推薦用於非常大的文件中TechNet 這似乎的確避免文件碎片(而窗內一個簡單的文件副本10 Explorer支持分段我的檔案!)

沒有緩衝副本似乎比這個相反的方式異步拷貝。或者有沒有辦法異步複製而不緩衝?

這裏它是我當前的代碼異步複製。我讓默認的緩衝區大小爲81920字節,即10 * 1024 *大小(int64)。

我正在使用NTFS文件系統,因此有4096個字節的簇。

編輯:我更新中用SetLength函數的代碼的建議,增加了FileOptions異步在創建destinationStream和修復設置屬性後設定的時間(否則,拋出異常的只讀文件)

 int bufferSize = 81920; 
     try 
     { 
      using (FileStream sourceStream = source.OpenRead()) 
      { 
       // Remove existing file first 
       if (File.Exists(destinationFullPath)) 
        File.Delete(destinationFullPath); 

       using (FileStream destinationStream = File.Create(destinationFullPath, bufferSize, FileOptions.Asynchronous)) 
       { 
        try 
        {        
         destinationStream.SetLength(sourceStream.Length); // avoid file fragmentation! 
         await sourceStream.CopyToAsync(destinationStream, bufferSize, cancellationToken); 
        } 
        catch (OperationCanceledException) 
        { 
         operationCanceled = true; 
        } 
       } // properly disposed after the catch 
      } 
     } 
     catch (IOException e) 
     { 
      actionOnException(e, "error copying " + source.FullName); 
     } 

     if (operationCanceled) 
     { 
      // Remove the partially written file 
      if (File.Exists(destinationFullPath)) 
       File.Delete(destinationFullPath); 
     } 
     else 
     { 
      // Copy meta data (attributes and time) from source once the copy is finished 
      File.SetCreationTimeUtc(destinationFullPath, source.CreationTimeUtc); 
      File.SetLastWriteTimeUtc(destinationFullPath, source.LastWriteTimeUtc); 
      File.SetAttributes(destinationFullPath, source.Attributes); // after set time if ReadOnly! 
     } 

我還擔心我的代碼上最後的File.SetAttributes和Time會增加文件碎片。

是否有正確的方法來創建1:1的異步文件副本而沒有任何文件碎片,即要求HDD文件蒸汽只能獲得連續的扇區?

有關文件碎片的其他主題,如How can I limit file fragmentation while working with .NET,建議以較大的塊增加文件大小,但似乎並不直接回答我的問題。

+0

你試過'destinationStream.Length = sourceStream.Length;'就在複製之前? –

+0

好主意,長度只是一個吸氣劑,但SetLength方法完成這項工作。看起來真的要在快速測試中避免分裂! 我在創建destinationStream時也看到了FileOptions。不知道是否異步或WriteThrough可能是一個不錯的選擇 – EricBDev

回答

-1

考慮漢斯帕桑特答案, 在我上面的代碼,以

替代
destinationStream.SetLength(sourceStream.Length); 

是,如果我的理解是正確:

byte[] writeOneZero = {0}; 
destinationStream.Seek(sourceStream.Length - 1, SeekOrigin.Begin); 
destinationStream.Write(writeOneZero, 0, 1); 
destinationStream.Seek(0, SeekOrigin.Begin); 

看來確實要鞏固副本。

可是一看的FileStream.SetLengthCore似乎它幾乎是相同的,求末,但沒有寫一個字節的源代碼:

private void SetLengthCore(long value) 
    { 
     Contract.Assert(value >= 0, "value >= 0"); 
     long origPos = _pos; 

     if (_exposedHandle) 
      VerifyOSHandlePosition(); 
     if (_pos != value) 
      SeekCore(value, SeekOrigin.Begin); 
     if (!Win32Native.SetEndOfFile(_handle)) { 
      int hr = Marshal.GetLastWin32Error(); 
      if (hr==__Error.ERROR_INVALID_PARAMETER) 
       throw new ArgumentOutOfRangeException("value", Environment.GetResourceString("ArgumentOutOfRange_FileLengthTooBig")); 
      __Error.WinIOError(hr, String.Empty); 
     } 
     // Return file pointer to where it was before setting length 
     if (origPos != value) { 
      if (origPos < value) 
       SeekCore(origPos, SeekOrigin.Begin); 
      else 
       SeekCore(0, SeekOrigin.End); 
     } 
    } 

反正不知道論文的方法保證沒有碎裂,但在在大多數情況下最不要回避。因此,自動碎片整理工具將以低性能費用完成工作。 我的初始代碼沒有這個Seek調用爲1 GB文件創建了數十萬個碎片,當碎片整理工具變爲活動狀態時,我的機器變慢了。

+0

我昨天覆制了一個100 GB的虛擬機文件,其中目標驅動器有足夠的空間(但是,目標是SSD,碎片不相關,因此它可能會改變Windows內核的結果)。 a)用窗10資源管理器/副本:對象文件有3個片段 B)中用SetLength函數():同3個片段 c)與以上/ writeOneZero /尋求+寫的代碼:只有1片 因此,該求+寫的確有道理! – EricBDev

3

我認爲,FileStream.SetLength是你所需要的。

+1

我也來到盧卡斯評論該解決方案。 它減少了很多碎片。 但是,並不完全,我仍然有一些文件在副本之後碎片化。與之前的狀態相比,這並不是什麼大不了的事,但不知道我能做得更好。 我們可以保證沒有碎片? – EricBDev

+1

您只能保證在每次複製操作之前格式化磁盤。 –

+0

@HenkHolterman你是對的,但另一方面,有可能在多個並行寫入的情況下減少碎片 –

2

但SetLength方法做這項工作

它不會做的工作。它只有更新目錄條目中的文件大小,它不分配任何羣集。親自看到這個最簡單的方法就是在一個非常大的文件上做這件事,比如100GB。請注意呼叫如何立即完成。只有當文件系統不能完成分配和寫入集羣的工作時,它纔是瞬間的。從文件讀取實際上是可能的,即使該文件不包含實際數據,文件系統也會返回二進制零。

這也會誤導任何報告碎片的實用程序。由於該文件沒有羣集,因此可能沒有碎片。所以它只是看起來像解決了你的問題。

強制分配集羣唯一可以做的事情是實際寫入文件。實際上可以通過一次寫入來分配100千兆字節的集羣。您必須使用Seek()來定位到Length-1,然後用Write()寫入單個字節。這將在一個非常大的文件上花費一段時間,它實際上不再是異步。

它會減少碎片的可能性並不大。您只是略微降低了寫入將被來自其他進程的寫入交織的風險。有一點,實際的寫作是由文件系統緩存懶洋洋地完成的。核心問題是,在開始編寫之前,卷已經被分割,在完成之後,它永遠不會更少碎片化。

最好的事情就是不要爲此煩惱。這些日子,Windows自動進行碎片整理,自從Vista開始。也許你想play with the scheduling,也許你要問更多關於它在superuser.com

+0

「這也會誤導任何報告碎片的實用程序。由於該文件沒有羣集,因此可能沒有碎片「 但最終文件被寫入。只用一個4GB的文件做了一次測試,佔用16k簇:在碎片整理工具的ClusterView中,所有文件看起來都是連續的。 – EricBDev

+0

請看我對應的答案,這是你的意思。正如所寫的,它似乎與SetLengh()一樣「立即」,似乎並沒有造成性能損失。 但它並不保證所有集羣都不會連續。 我剛剛測試過在只有90 GB可用的分區上覆制60 GB文件。 60 GB被複制,但在3個片段,因爲我的磁盤沒有自由連續的60 GB! (在中間佔用一些集羣) – EricBDev

+0

正如我在上面的回答中所評論的那樣,seek + write策略比SetLength更好地完成了100GB的VM複製:一個用seek +寫入,而另一個用SetLength()! – EricBDev