如何获取文件第一个字节在磁盘上的位置?
How to get the location of the first byte of the file on a disk?
我需要在 C# 应用程序中对给定 USB 磁盘上的每个文件进行校验和。我怀疑这里的瓶颈是磁盘的实际读取,所以我希望尽快完成。
我怀疑如果我可以按顺序读取磁盘上的文件,按照它们在磁盘上出现的实际顺序(假设驱动器没有碎片),这会快得多。
如何从标准路径中找到每个文件的信息?即给定一个位于 "F:\MyFile.txt" 的文件,我如何找到该文件在磁盘上的起始位置?
我是 运行 Windows 中的 C# 应用程序。
如果您的驱动器是 SSD 或基于记忆棒技术 - 算了吧。
记忆棒和其他类似设备一般都是基于SSD(或类似)技术,其中随机read/write访问的问题实际上不是问题。所以你可以枚举文件和 运行 你的校验和。
您可以尝试 运行在多个线程中执行此操作,但我不确定这是否可以加快进程,您可能需要对此进行测试。它也可能因设备而异。
奖金
@xanatos 提到了一个有趣的观点:"I always noticed that copying thousand of files on a memory stick is much slower than copying a single big file"
复制一个大文件确实比复制一堆小文件要快得多。原因(通常)不是因为文件彼此靠近,所以硬件更容易按顺序读取它们。问题出现在需要跟踪每个文件的 OS 上。
如果您曾经 运行 在 Windows 上 procmon,您会观察到大量的 FileCreates、FileReads 和 FileWrites。为了复制 100 个文件,OS 将打开每个文件,读取其内容,写入另一个文件,关闭两个文件 + 发送到文件系统的大量更新操作,例如更新两个文件的属性,更新两个文件的安全描述符,更新目录信息等。所以一个复制操作有很多卫星操作。
现在...我真的不知道它是否对你有用:
[StructLayout(LayoutKind.Sequential)]
public struct StartingVcnInputBuffer
{
public long StartingVcn;
}
public static readonly int StartingVcnInputBufferSizeOf = Marshal.SizeOf(typeof(StartingVcnInputBuffer));
[StructLayout(LayoutKind.Sequential)]
public struct RetrievalPointersBuffer
{
public uint ExtentCount;
public long StartingVcn;
public long NextVcn;
public long Lcn;
}
public static readonly int RetrievalPointersBufferSizeOf = Marshal.SizeOf(typeof(RetrievalPointersBuffer));
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern SafeFileHandle CreateFileW(
[MarshalAs(UnmanagedType.LPWStr)] string filename,
[MarshalAs(UnmanagedType.U4)] FileAccess access,
[MarshalAs(UnmanagedType.U4)] FileShare share,
IntPtr securityAttributes,
[MarshalAs(UnmanagedType.U4)] FileMode creationDisposition,
[MarshalAs(UnmanagedType.U4)] FileAttributes flagsAndAttributes,
IntPtr templateFile);
[DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true, CharSet = CharSet.Auto)]
static extern bool DeviceIoControl(IntPtr hDevice, uint dwIoControlCode,
ref StartingVcnInputBuffer lpInBuffer, int nInBufferSize,
out RetrievalPointersBuffer lpOutBuffer, int nOutBufferSize,
out int lpBytesReturned, IntPtr lpOverlapped);
// Returns a FileStream that can only Read
public static void GetStartLogicalClusterNumber(string fileName, out FileStream file, out long startLogicalClusterNumber)
{
SafeFileHandle handle = CreateFileW(fileName, FileAccess.Read | (FileAccess)0x80 /* FILE_READ_ATTRIBUTES */, FileShare.Read, IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero);
if (handle.IsInvalid)
{
throw new Win32Exception();
}
file = new FileStream(handle, FileAccess.Read);
var svib = new StartingVcnInputBuffer();
int error;
RetrievalPointersBuffer rpb;
int bytesReturned;
DeviceIoControl(handle.DangerousGetHandle(), (uint)589939 /* FSCTL_GET_RETRIEVAL_POINTERS */, ref svib, StartingVcnInputBufferSizeOf, out rpb, RetrievalPointersBufferSizeOf, out bytesReturned, IntPtr.Zero);
error = Marshal.GetLastWin32Error();
switch (error)
{
case 38: /* ERROR_HANDLE_EOF */
startLogicalClusterNumber = -1; // empty file. Choose how to handle
break;
case 0: /* NO:ERROR */
case 234: /* ERROR_MORE_DATA */
startLogicalClusterNumber = rpb.Lcn;
break;
default:
throw new Win32Exception();
}
}
请注意,该方法将 return 一个 FileStream
您可以保持打开并用于读取文件,或者您可以轻松地将其修改为不 return 它(并且不创建它),然后在您想要对它进行哈希处理时重新打开该文件。
使用:
string[] fileNames = Directory.GetFiles(@"D:\");
foreach (string fileName in fileNames)
{
try
{
long startLogicalClusterNumber;
FileStream file;
GetStartLogicalClusterNumber(fileName, out file, out startLogicalClusterNumber);
}
catch (Exception e)
{
Console.WriteLine("Skipping: {0} for {1}", fileName, e.Message);
}
}
我正在使用此处描述的 API:https://web.archive.org/web/20160130161216/http://www.wd-3.com/archive/luserland.htm。该程序要简单得多,因为您只需要初始逻辑簇号(代码的第一个版本可以提取所有 LCN 范围,但它没有用,因为您必须从文件的第一个字节到最后一个字节进行哈希处理)。请注意,空文件(长度为 0 的文件)没有分配任何簇。集群的函数 returns -1
(ERROR_HANDLE_EOF
)。您可以选择如何处理。
我需要在 C# 应用程序中对给定 USB 磁盘上的每个文件进行校验和。我怀疑这里的瓶颈是磁盘的实际读取,所以我希望尽快完成。
我怀疑如果我可以按顺序读取磁盘上的文件,按照它们在磁盘上出现的实际顺序(假设驱动器没有碎片),这会快得多。
如何从标准路径中找到每个文件的信息?即给定一个位于 "F:\MyFile.txt" 的文件,我如何找到该文件在磁盘上的起始位置?
我是 运行 Windows 中的 C# 应用程序。
如果您的驱动器是 SSD 或基于记忆棒技术 - 算了吧。
记忆棒和其他类似设备一般都是基于SSD(或类似)技术,其中随机read/write访问的问题实际上不是问题。所以你可以枚举文件和 运行 你的校验和。
您可以尝试 运行在多个线程中执行此操作,但我不确定这是否可以加快进程,您可能需要对此进行测试。它也可能因设备而异。
奖金
@xanatos 提到了一个有趣的观点:"I always noticed that copying thousand of files on a memory stick is much slower than copying a single big file"
复制一个大文件确实比复制一堆小文件要快得多。原因(通常)不是因为文件彼此靠近,所以硬件更容易按顺序读取它们。问题出现在需要跟踪每个文件的 OS 上。
如果您曾经 运行 在 Windows 上 procmon,您会观察到大量的 FileCreates、FileReads 和 FileWrites。为了复制 100 个文件,OS 将打开每个文件,读取其内容,写入另一个文件,关闭两个文件 + 发送到文件系统的大量更新操作,例如更新两个文件的属性,更新两个文件的安全描述符,更新目录信息等。所以一个复制操作有很多卫星操作。
现在...我真的不知道它是否对你有用:
[StructLayout(LayoutKind.Sequential)]
public struct StartingVcnInputBuffer
{
public long StartingVcn;
}
public static readonly int StartingVcnInputBufferSizeOf = Marshal.SizeOf(typeof(StartingVcnInputBuffer));
[StructLayout(LayoutKind.Sequential)]
public struct RetrievalPointersBuffer
{
public uint ExtentCount;
public long StartingVcn;
public long NextVcn;
public long Lcn;
}
public static readonly int RetrievalPointersBufferSizeOf = Marshal.SizeOf(typeof(RetrievalPointersBuffer));
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern SafeFileHandle CreateFileW(
[MarshalAs(UnmanagedType.LPWStr)] string filename,
[MarshalAs(UnmanagedType.U4)] FileAccess access,
[MarshalAs(UnmanagedType.U4)] FileShare share,
IntPtr securityAttributes,
[MarshalAs(UnmanagedType.U4)] FileMode creationDisposition,
[MarshalAs(UnmanagedType.U4)] FileAttributes flagsAndAttributes,
IntPtr templateFile);
[DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true, CharSet = CharSet.Auto)]
static extern bool DeviceIoControl(IntPtr hDevice, uint dwIoControlCode,
ref StartingVcnInputBuffer lpInBuffer, int nInBufferSize,
out RetrievalPointersBuffer lpOutBuffer, int nOutBufferSize,
out int lpBytesReturned, IntPtr lpOverlapped);
// Returns a FileStream that can only Read
public static void GetStartLogicalClusterNumber(string fileName, out FileStream file, out long startLogicalClusterNumber)
{
SafeFileHandle handle = CreateFileW(fileName, FileAccess.Read | (FileAccess)0x80 /* FILE_READ_ATTRIBUTES */, FileShare.Read, IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero);
if (handle.IsInvalid)
{
throw new Win32Exception();
}
file = new FileStream(handle, FileAccess.Read);
var svib = new StartingVcnInputBuffer();
int error;
RetrievalPointersBuffer rpb;
int bytesReturned;
DeviceIoControl(handle.DangerousGetHandle(), (uint)589939 /* FSCTL_GET_RETRIEVAL_POINTERS */, ref svib, StartingVcnInputBufferSizeOf, out rpb, RetrievalPointersBufferSizeOf, out bytesReturned, IntPtr.Zero);
error = Marshal.GetLastWin32Error();
switch (error)
{
case 38: /* ERROR_HANDLE_EOF */
startLogicalClusterNumber = -1; // empty file. Choose how to handle
break;
case 0: /* NO:ERROR */
case 234: /* ERROR_MORE_DATA */
startLogicalClusterNumber = rpb.Lcn;
break;
default:
throw new Win32Exception();
}
}
请注意,该方法将 return 一个 FileStream
您可以保持打开并用于读取文件,或者您可以轻松地将其修改为不 return 它(并且不创建它),然后在您想要对它进行哈希处理时重新打开该文件。
使用:
string[] fileNames = Directory.GetFiles(@"D:\");
foreach (string fileName in fileNames)
{
try
{
long startLogicalClusterNumber;
FileStream file;
GetStartLogicalClusterNumber(fileName, out file, out startLogicalClusterNumber);
}
catch (Exception e)
{
Console.WriteLine("Skipping: {0} for {1}", fileName, e.Message);
}
}
我正在使用此处描述的 API:https://web.archive.org/web/20160130161216/http://www.wd-3.com/archive/luserland.htm。该程序要简单得多,因为您只需要初始逻辑簇号(代码的第一个版本可以提取所有 LCN 范围,但它没有用,因为您必须从文件的第一个字节到最后一个字节进行哈希处理)。请注意,空文件(长度为 0 的文件)没有分配任何簇。集群的函数 returns -1
(ERROR_HANDLE_EOF
)。您可以选择如何处理。