C# 应用程序之间的Windows目录大小差异

C# 应用程序之间的Windows目录大小差异,c#,windows,C#,Windows,我正在编写一个32位c#应用程序,它通过从kernal32.dll FindFirstFile获取文件信息来返回目录的总体大小。这已经战胜了以常规方式枚举每个目录,并且我能够将资源使用保持在极低的水平 下面简要介绍了该方法的工作原理: 步骤1-枚举根目录获取所有子目录,并使用FindFirstFile收集此目录中每个文件的大小信息 步骤2-生成子线程(最多20个)对子目录执行步骤1 第3步-递归,直到目录用尽,所有文件信息都已收集 这可以在下面的代码示例中看到,其中FileSystem.Ge

我正在编写一个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);
        }
    }
这段代码在枚举大多数目录时执行得非常完美。我能够递归3TB文件共享,在大约8分钟内获得总文件大小和文件数,同时保持cpu低于3%,并使用15MB内存

现在问题来了

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

例如:

假设我正在查看目录D:\TestDir,它是DFSR复制到另一台服务器的。 Windows称该目录为2949944019217字节,或磁盘上的2974186774528字节(分别为2.68 TB或2.70 TB)。 我的程序说这个目录是3009619048759字节或2.737 TB。 FSRM表示,同一目录上的配额设置使用率为2.71 TB

我知道这种差异部分是由于Windows没有在其大小中包含隐藏文件,但是当我将目录中隐藏文件的总大小(87GB)添加到Windows值时,我得到了~2.78 GB,这仍然与我的值不同。有没有人能解释一下,是什么原因导致了这些尺寸差异?还有,有人知道FSRM是如何确定配额使用的吗


最终,我想用一个使用我的数据的监控系统来取代FSRM配额,但如果我的数据与Windows所说的不一致,我可能会得到磁盘使用的错误警报

经过一些深入测试,这最终成为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
根据文件信息的大小:18158717658字节
根据WIN32_FIND_数据的大小:978848478字节

文件2
根据文件信息的大小:18211490304字节
根据WIN32_FIND_数据的大小:1031621124字节

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


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

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

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

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


这个问题比看起来更难。

我根本没有研究过,但是对于小文件,一个是报告数据的实际大小,另一个是报告分配的磁盘空间的大小吗?我相信一个文件的最小分配大小是4K,不管其中的数据有多少。感谢Bobson的评论,我相信Windows“磁盘大小”会考虑到这一点,从而产生2.70 TB的值。我这样说是因为如果你看一个4K以下的文件的属性,它会说“大小:x字节,磁盘大小:4.00KB”。总而言之,最好能确切地了解为什么FileInfo(或者更确切地说是WinAPI函数GetFileAttributesEx,这是FileInfo内部使用的函数,如果可用的话)给出了一个不同的结果。我同意,虽然我发现了一个工作,我不会考虑这个问题。我将继续研究这个问题,并发布任何我能发现的信息。我的应用程序是32位的,我当前的测试是在一个64位的系统上。我的代码通过检查进程是否是64位和USEN来处理文件系统重定向。g Wow64DisableWow64FsRedirection和Wow64RevertWow64FsRedirection方法。硬链接是一个很好的研究方法,我将编写一些代码来测试这个理论。
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
internal static extern SafeFindHandle FindFirstFile(string lpFileName, out WIN32_FIND_DATA lpFindFileData)