我正在编写一个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分钟内递归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.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所说的不一致,我可能会收到有关磁盘使用情况的错误警报。
答案 0 :(得分:1)
经过深入测试后,这最终成了kernal32.dll FindFirstFile方法的错误:
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
internal static extern SafeFindHandle FindFirstFile(string lpFileName, out WIN32_FIND_DATA lpFindFileData)
此函数返回一个类&#34; WIN32_FIND_DATA&#34;其中包含有关特定文件的信息,包括名称,大小,上次修改时间等。我运行了一个测试,我将此函数返回的大小与System.IO.FileInfo类返回的大小进行比较,发现一些明显的差异。非常小的文件集。对包含约150万个文件的文件共享运行时,两个文件的大小明显不同,如下所示:
文件1
根据FileInfo的大小:18158717658字节
根据WIN32_FIND_DATA的大小:978848478字节
文件2
根据FileInfo的大小:18211490304字节
根据WIN32_FIND_DATA的大小:1031621124字节
在这两种情况下,尺寸差异几乎都是16 GB。
要解决此问题,我将继续使用Kernal32.dll函数来获取文件路径,但使用FileInfo来获取大小。这似乎在不影响性能的情况下产生了良好的效果。
答案 1 :(得分: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
结构中的信息。
这个问题比它看起来更难。