【问题标题】:How to get the location of the first byte of the file on a disk?如何获取磁盘上文件第一个字节的位置?
【发布时间】:2015-06-18 17:53:25
【问题描述】:

我需要在 C# 应用程序中对给定 USB 磁盘上的每个文件进行校验和。我怀疑这里的瓶颈是磁盘的实际读取,所以我希望尽快做到这一点。

我怀疑如果我可以按照它们在磁盘上出现的实际顺序顺序读取磁盘上的文件(假设驱动器没有碎片),这会更快。

如何从每个文件的标准路径中找到该信息?即给定一个位于“F:\MyFile.txt”的文件,我怎样才能找到该文件在磁盘上的起始位置?

我正在 Windows 中运行 C# 应用程序。

【问题讨论】:

  • 你可以stackoverflow.com/questions/11934550/… ...请注意,我认为这不是一个好主意...更正...我确实认为这是一个坏主意
  • @xanatos 为什么这是个坏主意?
  • @oleksii 我什至不确定您确实需要什么权限...并且仅因为高级方法很慢而对磁盘进行低级操作不是我永远不会做的事情。有高级方法和低级方法是有原因的。
  • @xanatos wd-3.com 是一个“坏”网站,我建议删除该链接。
  • @andreaplanet 删除了链接...它已过期 :-( 将链接替换为 web.archive.org/web/20160130161216/http://www.wd-3.com/archive/…

标签: c# windows file filesystems ntfs


【解决方案1】:

现在...我真的不知道它是否对你有用:

[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();
    }
}

请注意,该方法将返回一个FileStream,您可以保持打开并使用它来读取文件,或者您可以轻松地将其修改为不返回(并且不创建它),然后在需要时重新打开文件哈希它。

使用方法:

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 的文件)没有分配任何簇。该函数返回集群的-1 (ERROR_HANDLE_EOF)。您可以选择如何处理。

【讨论】:

  • 非常非常有帮助,谢谢!我才刚刚开始阅读那篇文章,更不用说理解所有内容了......
  • 我唯一的问题是它似乎不仅仅是返回-1的空文件,而是非常小的文件......我理解没有集群的空文件的逻辑,这对非常小的文件有何影响?我说的是 1 行文本,96 字节,txt 文件。
  • @GoldieLocks ntfs.com/ntfs_optimization.htm 在 NTFS 上,如果文件足够小,它可以存储在 MFT 记录本身中,而无需使用额外的集群。
【解决方案2】:

如果您的驱动器是 SSD 或基于记忆棒技术 - 算了。

记忆棒等类似设备一般都是基于SSD(或类似)技术的,随机读写访问的问题其实不成问题。所以你可以枚举文件并运行你的校验和。

您可以尝试在多个线程中运行它,但我不确定这是否可以加快进程,这可能需要您进行测试。它也可能因设备而异。

奖金
@xanatos 提到了一个有趣的点:“我一直注意到在记忆棒上复制数千个文件比复制单个大文件要慢得多”

复制一个大文件确实比复制一堆小文件要快得多。原因(通常)不是因为文件彼此靠近,因此硬件更容易按顺序读取它们。问题在于需要跟踪每个文件的操作系统。

如果您曾经在 Windows 上运行procmon,您会观察到大量的 FileCreates、FileReads 和 FileWrites。为了复制 100 个文件,操作系统将打开每个文件,读取其内容,写入另一个文件,关闭两个文件 + 发送到文件系统的大量更新操作,例如更新两个文件的属性,更新安全描述符两个文件,更新目录信息等。所以一个复制操作有很多卫星操作。

【讨论】:

  • 你可能是对的,但从经验测试来看,我一直注意到在记忆棒上复制数千个文件比复制单个大文件要慢得多...
  • 将用于此的驱动器都是 1TB 2.5 英寸标准非 SSD USB3 硬盘驱动器,如果有区别的话......
  • @GoldieLocks 是的,那很重要。我将更新我的答案 - 它仅适用于 ssd 和记忆棒。我不知道如何在旋转磁盘上按顺序(在低级别)读取文件。
猜你喜欢
  • 2019-11-17
  • 2011-04-14
  • 2012-02-06
  • 2021-07-30
  • 2017-06-10
  • 2018-10-24
  • 2022-07-05
  • 2018-12-04
  • 1970-01-01
相关资源
最近更新 更多