2

我正在编写一个 32 位 c# 应用程序,它通过从 kernal32.dll FindFirstFile 获取文件信息来返回目录的整体大小。这战胜了以常规方式枚举每个目录,并且我能够将资源使用率保持在极低的水平。

其工作原理的快速概述如下:

  • 第 1 步 - 枚举根目录获取所有子目录并使用 FindFirstFile 收集此目录中每个文件的大小信息。
  • 第 2 步 - 生成子线程(最多 20 个)以针对子目录执行第 1 步
  • 第 3 步 - 递归直到用尽目录并收集所有文件信息。

这可以在以下代码示例中看到,其中 FileSystem.GetFiles 是我的类,它利用 kernal32 方法获取文件信息。

      private static void recurseDirectories(string directoryA, bool paramInitialPass)
    {
        try
        {
            string[] currentDirs;
            if (paramInitialPass)
            {
                currentDirs = new string[1];
                currentDirs[0] = rootDirectory;
            }
            else
                currentDirs = Directory.GetDirectories(directoryA);

            for (int i = 0; i < currentDirs.Length; i++)
            {

                string threadInfo = currentDirs[i];
                numThreadsQueued++;
                ThreadPool.QueueUserWorkItem(new WaitCallback(getDirectoryFileInformation), (object)threadInfo);
                while (numThreadsQueued - directoriesProcessed > 20)
                {
                    Thread.Sleep(30);
                }
                if (paramInitialPass)
                    recurseDirectories(directoryA, false);
                else
                    recurseDirectories(currentDirs[i], false);
            }
        }
        catch
        {

        }
        return;
    }


    private static void getDirectoryFileInformation(object paramDirectoryFilePathA)
    {
        try
        {
            string directoryPathA = (string)paramDirectoryFilePathA;
            List<FileData> filesDirectoryA = new List<FileData>();
            if (Directory.Exists(directoryPathA))
            {
                    filesDirectoryA = FileSystem.GetFiles(directoryPathA);
            }
            foreach(FileData file in filesDirectoryA)
            {
                Interlocked.Add(ref sizeOfFiles, file.Size);
                Interlocked.Increment(ref numberOfFiles);
            }               
        }
        catch (Exception e)
        {

        }
        finally
        {
            Interlocked.Increment(ref directoriesProcessed);
        }
    }

使用以下代码调用这两个方法:

 ThreadPool.SetMaxThreads(30, 500);
 Thread.CurrentThread.Priority = ThreadPriority.Normal;
 rootDirectory = share["Path"].ToString();
 recurseDirectories(share["Path"].ToString(), true);
 while (numThreadsQueued != directoriesProcessed)
 {
        Thread.Sleep(1000);
 }

此代码在枚举大多数目录时表现完美。我能够递归 3TB 文件共享,在大约 8 分钟内获得总文件大小和文件数量,同时将 cpu 保持在 3% 以下并使用 15MB 内存。

现在问题来了...

在获取小目录(1-200 GB)的大小时,我看不到 Windows 在查看目录属性时所说的任何重大差异。但是,我注意到在获取大目录(2-3TB)的大小时存在一些主要差异。

例如:

假设我正在查看 DFSR 复制到另一台服务器的目录 D:\TestDir。Windows 说这个目录是 2,949,944,019,217 字节,或磁盘上的 2,974,186,774,528 字节(分别为 2.68 TB 或 2.70 TB)。我的程序说这个目录是 3,009,619,048,759 字节或 2.737 TB。FSRM 说同一目录上的配额设置有 2.71 TB 的使用量。

我知道差异部分是由于 Windows 的大小不包括隐藏文件,但是当我将目录中隐藏文件的总大小 (87GB) 添加到 Windows 值时,我得到 ~2.78 GB,这仍然与我的值不同。任何人都可以阐明我造成这些尺寸差异的其他原因吗?另外,有谁知道 FSRM 如何确定配额使用情况?

最终,我想用一个使用我的数据的监控系统替换 FSRM 配额,但如果我的数据与 Windows 所说的不相符,我可能会收到有关磁盘使用情况的错误警报。

4

2 回答 2

1

经过一些深入测试后,这最终成为 kernal32.dll FindFirstFile 方法的错误:

[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
internal static extern SafeFindHandle FindFirstFile(string lpFileName, out WIN32_FIND_DATA lpFindFileData)

此函数返回一个类“WIN32_FIND_DATA”,其中包含有关特定文件的信息,包括名称、大小、上次修改时间等。我运行了一个测试,将这个函数返回的大小与 System.IO.FileInfo 返回的大小进行比较类,并在非常小的一组文件上发现了一些明显的差异。当对包含约 150 万个文件的文件共享运行此程序时,两个文件的大小明显不同,如下所示:

文件 1
根据 FileInfo 的大小:18158717658 字节
根据 WIN32_FIND_DATA 的大小:978848478 字节

文件 2
根据 FileInfo 的大小:18211490304 字节
根据 WIN32_FIND_DATA 的大小:1031621124 字节

在这两种情况下,大小差异几乎正好是 16 GB。

为了解决这个问题,我仍将使用 Kernal32.dll 函数来获取文件路径,但使用 FileInfo 来获取大小。这似乎在不影响性能的情况下产生了良好的结果。

于 2013-01-02T14:41:46.480 回答
0

您提到您的应用程序被编译为 32 位。你是在 64 位系统上运行它吗?您可能会遇到文件系统重定向,例如,当 32 位应用程序尝试读取C:\Windows\System32您实际获取C:\Windows\SysWOW64的 . 您可能需要 p/invoke Wow64DisableWow64FsRedirection

因此,FileInfo可能会正确报告非常大文件的大小,但是尽管这可能会使您的答案保持一致,但它们仍然不正确。你为什么要使用 p/invoke 呢?

此外,NTFS 文件系统支持单个文件具有多个目录条目的硬链接。但它只为其内容使用磁盘空间一次。您可以通过读取“链接计数”元数据并将文件大小除以该字段来处理此问题。在这种情况下,您将需要 p/invoke Win32 API。您可能还想使用GetFileInformationByHandleEx(在使用查询权限打开文件后)而不是WIN32_FIND_DATA结构中的信息。

这个问题比看起来更难。

于 2013-01-02T15:22:28.060 回答