2017-01-05 55 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