20

使用 C#,我正在查找目录的总大小。逻辑是这样的:获取文件夹内的文件。总结总规模。查找是否有子目录。然后进行递归搜索。

我也尝试了另一种方法:使用 FSO ( obj.GetFolder(path).Size)。这两种方法在时间上没有太大差异。

现在的问题是,我在一个特定文件夹中有数万个文件,并且至少需要 2 分钟才能找到文件夹大小。此外,如果我再次运行该程序,它会很快发生(5 秒)。我认为 Windows 正在缓存文件大小。

有什么办法可以减少我第一次运行程序所花费的时间吗?

4

8 回答 8

36

如果摆弄了一段时间,尝试并行化它,并且令人惊讶的是 - 它在我的机器上加速(在四核上最多 3 倍),不知道它是否在所有情况下都有效,但试一试。 ..

.NET4.0 代码(或将 3.5 与 TaskParallelLibrary 一起使用)

    private static long DirSize(string sourceDir, bool recurse)
    {
        long size = 0;
        string[] fileEntries = Directory.GetFiles(sourceDir);

        foreach (string fileName in fileEntries)
        {
            Interlocked.Add(ref size, (new FileInfo(fileName)).Length);
        }

        if (recurse)
        {
            string[] subdirEntries = Directory.GetDirectories(sourceDir);

            Parallel.For<long>(0, subdirEntries.Length, () => 0, (i, loop, subtotal) =>
            {
                if ((File.GetAttributes(subdirEntries[i]) & FileAttributes.ReparsePoint) != FileAttributes.ReparsePoint)
                {
                    subtotal += DirSize(subdirEntries[i], true);
                    return subtotal;
                }
                return 0;
            },
                (x) => Interlocked.Add(ref size, x)
            );
        }
        return size;
    }
于 2010-06-05T17:02:34.413 回答
10

硬盘是一种有趣的野兽——顺序访问(例如读取一个大的连续文件)非常快,图 80 兆字节/秒。但是随机访问非常慢。这就是您遇到的问题-递归到文件夹中不会读取太多(就数量而言)数据,但需要很多随机读取。您第二次看到 zippy perf 的原因是因为 MFT 仍在 RAM 中(您对缓存的想法是正确的)

我见过的实现这一目标的最佳机制是自己扫描 MFT。这个想法是你在一个线性通道中读取和解析 MFT,构建你需要的信息。最终结果将是非常接近 15 秒的 HD 非常完整。

一些不错的阅读:NTFSInfo.exe - http://technet.microsoft.com/en-us/sysinternals/bb897424.aspx Windows Internals - http://www.amazon.com/Windows%C2%AE-Internals-Including- Windows-PRO-Developer/dp/0735625301/ref=sr_1_1?ie=UTF8&s=books&qid=1277085832&sr=8-1

FWIW:这种方法非常复杂,因为在 Windows(或我知道的任何操作系统)中确实没有一个很好的方法来做到这一点 - 问题是找出需要哪些文件夹/文件的行为需要很多头脑磁盘上的运动。微软很难为你描述的问题建立一个通用的解决方案。

于 2010-06-21T02:05:52.983 回答
7

最简洁的答案是不。Windows 可以使目录大小计算更快的方法是在每次文件写入时更新目录大小和所有父目录大小。但是,这会使文件写入操作变慢。由于文件写入比读取目录大小更常见,因此这是一个合理的权衡。

我不确定正在解决什么确切问题,但如果是文件系统监控,可能值得一试:http: //msdn.microsoft.com/en-us/library/system.io.filesystemwatcher.aspx

于 2010-06-05T06:54:59.993 回答
2

使用任何方法扫描包含数万个文件的文件夹时,性能都会受到影响。

  • 使用 Windows API FindFirstFile... 和 FindNextFile... 函数提供最快的访问。

  • 由于编组开销,即使您使用 Windows API 函数,性能也不会提高。框架已经封装了这些 API 函数,所以自己做是没有意义的。

  • 您如何处理任何文件访问方法的结果决定了您的应用程序的性能。例如,即使您使用 Windows API 函数,更新列表框也会影响性能。

  • 您无法将执行速度与 Windows 资源管理器进行比较。根据我的实验,我相信 Windows 资源管理器在许多情况下直接从文件分配表中读取。

  • 我知道对文件系统的最快访问是DIR命令。您无法将性能与此命令进行比较。它肯定直接从文件分配表中读取(可能使用 BIOS)。

  • 是的,操作系统缓存文件访问。

建议

  • 我想知道BackupRead对你的情况是否有帮助?

  • 如果你掏出 DIR 并捕获然后解析它的输出怎么办?(您并没有真正进行解析,因为每个 DIR 行都是固定宽度的,因此只需调用子字符串即可。)

  • 如果您DIR /B > NULL在后台线程上运行然后运行您的程序怎么办?在 DIR 运行时,您将受益于缓存文件访问。

于 2010-06-16T02:22:59.773 回答
2

根据 spookycoder 的回答,我发现这种变化(使用DirectoryInfo)至少快 2 倍(在复杂的文件夹结构上快 10 倍!):

    public static long CalcDirSize(string sourceDir, bool recurse = true)
    {
        return _CalcDirSize(new DirectoryInfo(sourceDir), recurse);
    }

    private static long _CalcDirSize(DirectoryInfo di, bool recurse = true)
    {
        long size = 0;
        FileInfo[] fiEntries = di.GetFiles();
        foreach (var fiEntry in fiEntries)
        {
            Interlocked.Add(ref size, fiEntry.Length);
        }

        if (recurse)
        {
            DirectoryInfo[] diEntries = di.GetDirectories("*.*", SearchOption.TopDirectoryOnly);
            System.Threading.Tasks.Parallel.For<long>(0, diEntries.Length, () => 0, (i, loop, subtotal) =>
            {
                if ((diEntries[i].Attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint) return 0;
                subtotal += __CalcDirSize(diEntries[i], true);
                return subtotal;
            },
                (x) => Interlocked.Add(ref size, x)
            );

        }
        return size;
    }
于 2018-11-13T12:36:53.803 回答
1

我认为它不会有太大变化,但是如果您使用 API 函数FindFirstFileNextFile执行此操作,它可能会更快一些。

但是,我认为没有任何真正快速的方法可以做到这一点。出于比较目的,您可以尝试dir /a /x /s > dirlist.txt在 Windows 资源管理器中列出目录以查看它们的速度,但我认为它们将类似于FindFirstFile.

PInvoke有一个如何使用 API 的示例。

于 2010-06-05T06:55:54.143 回答
0

拥有数以万计的文件,您不会因正面攻击而获胜。您需要尝试在解决方案上更具创意。有了这么多文件,您甚至可能会发现,在您计算大小的时间里,文件已更改,您的数据已经错误。

因此,您需要将负载移动到其他地方。对我来说,答案是使用System.IO.FileSystemWatcher和编写一些代码来监视目录并更新索引。

编写可配置为监视一组目录并将结果写入共享输出文件的 Windows 服务应该只需要很短的时间。您可以让服务在启动时重新计算文件大小,但是只要System.IO.FileSystemWatcher. 监视目录的好处是您只对小的更改感兴趣,这意味着您的数据有更高的正确率(请记住所有数据都是陈旧的!)

然后,唯一需要注意的是,您将有多个资源都试图访问生成的输出文件。所以只要确保你考虑到这一点。

于 2010-06-17T11:06:01.953 回答
0

我放弃了 .NET 实现(出于性能原因)并使用了本机函数 GetFileAttributesEx(...)

试试这个:

[StructLayout(LayoutKind.Sequential)]
public struct WIN32_FILE_ATTRIBUTE_DATA
{
    public uint fileAttributes;
    public System.Runtime.InteropServices.ComTypes.FILETIME creationTime;
    public System.Runtime.InteropServices.ComTypes.FILETIME lastAccessTime;
    public System.Runtime.InteropServices.ComTypes.FILETIME lastWriteTime;
    public uint fileSizeHigh;
    public uint fileSizeLow;
}

public enum GET_FILEEX_INFO_LEVELS
{
    GetFileExInfoStandard,
    GetFileExMaxInfoLevel
}

public class NativeMethods {
    [DllImport("KERNEL32.dll", CharSet = CharSet.Auto)]
    public static extern bool GetFileAttributesEx(string path, GET_FILEEX_INFO_LEVELS  level, out WIN32_FILE_ATTRIBUTE_DATA data);

}

现在只需执行以下操作:

WIN32_FILE_ATTRIBUTE_DATA data;
if(NativeMethods.GetFileAttributesEx("[your path]", GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, out data)) {

     long size = (data.fileSizeHigh << 32) & data.fileSizeLow;
}
于 2010-06-21T10:05:06.330 回答