C#I / O异步(copyAsync):如何避免文件碎片?

时间:2017-01-05 20:54:33

标签: c# io async-await fragmentation

在磁盘之间复制大文件的工具中,我替换了 System.IO.Stream.CopyToAsync的System.IO.FileInfo.CopyTo方法。 这允许在复制期间更快的复制和更好的控制,例如,我可以停止复制。 但这会造成复制文件的更多碎片。当我复制数百兆字节的文件时,它特别烦人。

如何在复制过程中避免磁盘碎片?

使用xcopy命令,/ j开关可以复制文件而不进行缓冲。建议用于TechNet中的超大文件 它似乎确实避免了文件碎片(而Windows 10资源管理器中的一个简单的文件复制DOES会碎片化我的文件!)

没有缓冲的副本似乎与此异步副本相反。或者有没有办法在没有缓冲的情况下进行异步复制?

这是我目前的异步拷贝代码。我让默认缓冲区大小为81920字节,即10 * 1024 *大小(int64)。

我正在使用NTFS文件系统,因此有4096个字节的簇。

编辑:我按照建议使用SetLength更新代码,在创建destinationStream时添加了FileOptions异步,并在设置时间后修复设置属性(否则,为ReadOnly文件抛出异常)

        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的异步文件副本而没有任何文件碎片,即询问硬盘文件是否只获得连续的扇区?

有关文件碎片的其他主题如How can I limit file fragmentation while working with .NET建议以较大的块增加文件大小,但它似乎不是我问题的直接答案。

3 个答案:

答案 0 :(得分:3)

我认为,FileStream.SetLength就是您所需要的。

答案 1 :(得分:3)

  

但是SetLength方法完成了这项工作

它不能完成这项工作。 更新目录条目中的文件大小,它不分配任何群集。最简单的方法就是在一个非常大的文件上执行此操作,例如100千兆字节。请注意调用如何立即完成 。只有它可以是即时的方式是文件系统不执行分配和写入集群的工作。实际读取文件是可能的,即使文件不包含实际数据,文件系统也只返回二进制零。

这也会误导任何报告碎片的实用程序。由于文件没有集群,因此不会出现碎片。所以看起来你只是解决了你的问题。

强制分配集群的唯一办法就是实际写入文件。事实上,只需一次写入就可以分配100千兆字节的集群。必须使用Seek()定位到Length-1,然后用Write()写一个字节。这在一个非常大的文件上需要一段时间,实际上不再是异步。

它减少碎片的可能性并不大。您只是稍微降低了写入将被其他进程的写入交错的风险。有点,实际的写作是由文件系统缓存懒惰完成的。核心问题是在您开始写作之前卷已经碎片化,在您完成之后它将永远不会碎片化。

最好的办法就是不要担心它。最近在Windows上进行碎片整理是自Vista以来的。也许你想play with the scheduling,也许你想在superuser.com上询问更多关于它的信息

答案 2 :(得分:-1)

考虑到Hans Passant的回答, 在上面的代码中,

的替代方案
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文件创建了数十万个片段,在碎片整理工具激活时减慢了我的机器。