我正在写一个32位的C#应用程序,通过从kernal32.dll FindFirstFile获取文件信息来返回目录的整体大小。 这已经胜过枚举每个目录以正常的方式,我能够保持资源使用非常低。
如何工作的快速概述如下:
这可以在以下代码示例中看到,其中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); }
枚举大多数目录时,此代码已完美无瑕地执行。 我可以在8分钟内caching一个3TB的文件共享文件大小和文件数量,同时保持CPU低于3%,并使用15MB的内存。
现在问题来了…
当获取小目录的大小(1-200 GB)时,我看不到Windows在查看目录属性时所说的主要差异。 但是,当获得大型目录的大小(2-3TB)时,我注意到了一些主要的差异。
例如:
假设我正在查看目录D:\ TestDir这是DFSR复制到另一台服务器。 Windows表示这个目录是2,949,944,019,217字节,或者磁盘上的2,974,186,774,528字节(分别是2.68TB或2.70TB)。 我的程序说这个目录是3,009,619,048,759字节或2.737 TB。 FSRM表示,在同一目录上的配额设置有2.71 TB的使用量。
我知道这种差异部分是由于Windows不包含隐藏文件的大小,但是当我将目录(87GB)中隐藏文件的总大小添加到Windows值时,我得到了〜2.78GB,这仍然与我的值不同。 任何人都可以阐明我还有什么可能导致这些尺寸差异? 另外,有谁知道FSRM如何确定配额使用?
最后,我想用我的数据replace一个监控系统的FSRM配额,但是如果我的数据不符合Windows的说法,我可能会在磁盘使用率上得到错误警报。
经过一些深入的测试,最终成为kernal32.dll FindFirstFile方法的一个bug:
[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。
为了解决这个问题,我将继续使用coreal32.dll函数来获取文件路径,但使用FileInfo来获取大小。 这似乎在不影响性能的情况下产生好的结果。
你提到你的应用程序被编译为32位。 你在64位系统上运行它吗? 您可能遇到文件系统重定向,例如,当32位应用程序试图读取C:\Windows\System32
您实际上获得了C:\Windows\SysWOW64
。 你可能需要p / invoke Wow64DisableWow64FsRedirection
。
所以, FileInfo
可能会处理正确的报告大文件的大小,但尽管这可能会使你的答案一致,但它们仍然是不正确的。 为什么你用p / invoke呢?
另外,NTFS文件系统支持硬连接,其中单个文件具有多个目录条目。 但是一次只为其内容使用磁盘空间。 您可以通过阅读“链接计数”元数据并将文件大小除以该字段来处理。 在这种情况下,您将需要p /调用Win32 API。 您可能还想使用GetFileInformationByHandleEx
(打开具有查询权限的文件后)而不是WIN32_FIND_DATA
结构中的信息。
这个问题比看起来更困难。