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

C# I/O async (copyAsync): how to avoid file fragmentation?

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

复制时如何避免磁盘碎片?

使用 xcopy 命令,/j 开关无需缓冲即可复制文件。并且建议用于 TechNet 中的非常大的文件 似乎确实可以避免文件碎片化(而 windows 10 资源管理器中的一个简单文件副本确实会碎片化我的文件!)

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

这是我当前用于 ync 复制的代码。我让默认缓冲区大小为 81920 字节,即 10*1024*size(int64)。

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

编辑:我按照建议使用 SetLength 更新了代码,在创建 destinationStream 时添加了 FileOptions Async,并在设置时间后修复设置属性(否则,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 和时间会增加文件碎片。

是否有正确的方法来创建 1:1 没有任何文件碎片的异步文件副本,即要求 HDD 文件流只获得连续的扇区?

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

我想,FileStream.SetLength就是你需要的。

but the SetLength method does the job

它不起作用。它 更新目录条目中的文件大小,它不分配任何簇。亲自查看此内容的最简单方法是在非常大的文件(例如 100 GB)上执行此操作。请注意调用如何立即完成。只有当文件系统不执行分配和写入集群的工作时,它才能立即完成。从文件中读取实际上是可能的,即使文件不包含实际数据,文件系统只是 returns 二进制零。

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

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

减少碎片化的可能性不大。您只是在某种程度上降低了写入将被其他进程的写入交错的风险。某种程度上,实际写入是由文件系统缓存延迟完成的。核心问题是卷在你开始写之前是碎片化的,你写完之后它永远不会碎片化。

最好的办法就是不要为此烦恼。这些天在 Windows 上自动进行碎片整理,自 Vista 以来一直如此。也许您想 play with the scheduling,也许您想在 superuser.com

询问更多相关信息

考虑到 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 的文件创建了数十万个碎片,当碎片整理工具激活时减慢了我的机器。