在磁盘之间复制大文件的工具中,我替换了 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建议以较大的块增加文件大小,但它似乎不是我问题的直接答案。
答案 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文件创建了数十万个片段,在碎片整理工具激活时减慢了我的机器。