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 的文件创建了数十万个碎片,当碎片整理工具激活时减慢了我的机器。
在磁盘之间复制大文件的工具中,我替换了 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 的文件创建了数十万个碎片,当碎片整理工具激活时减慢了我的机器。