65.9K
CodeProject 正在变化。 阅读更多。
Home

更快的目录枚举器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.91/5 (86投票s)

2009年8月13日

CPOL

3分钟阅读

viewsIcon

374529

downloadIcon

25455

描述如何为目录中所有文件的属性创建一个明显更快的枚举器。

引言

.NET Framework 的 Directory 类包含用于查询目录中文件列表的方法。对于每个文件,您还可以查询文件的属性,例如大小、创建日期等。但是,当查询远程 PC 上的文件时,这可能效率很低;需要进行可能代价高昂的网络往返才能检索每个文件的属性。本文描述了一种更有效的实现,其速度大约快 3 倍。

背景

假设您正在编写一个需要找到目录中最近修改的文件的应用程序。为了实现这一点,您可能有一个类似于以下的函数

DateTime GetLastFileModifiedSlow(string dir)
{
    DateTime retval = DateTime.MinValue;
    
    string [] files = Directory.GetFiles(dir);
    for (int i=0; i<files.Length; i++)
    {
        DateTime lastWriteTime = File.GetLastWriteTime(files[i]);
        if (lastWriteTime > retval)
        {
            retval = lastWriteTime;
        }
    }
    
    return retval;
}

该函数当然可以工作,但它有一些非常差的性能特征

  1. GetFiles 必须分配一个可能非常大的数组。
  2. GetFiles 必须等待整个目录的条目返回后才能返回。
  3. 对于每个文件,都会向文件系统发送一个可能代价高昂的查询。没有尝试执行任何类型的批量查询。

您可能认为转换为 DirectoryInfo.GetFileSystemInfos 将改善第 3 项

DateTime GetLastFileModifiedSlow2(string dir)
{
    DateTime retval = DateTime.MinValue;
    
    DirectoryInfo dirInfo = new DirectoryInfo(dir);

    FileInfo[] files = dirInfo.GetFiles();
    for (int i=0; i<files.Length; i++)
    {
        if (files[i].LastWriteTime > retval)
        {
            retval = lastWriteTime;
        }
    }
    
    return retval;
}

但这并没有改变任何事情:GetFiles() 返回的对象没有用任何数据初始化,并且在第一次访问任何属性时都会查询文件系统。

使其更快

附加的测试应用程序在 FastDirectoryEnumerator.cs 中包含了 FastDirectoryEnumerator 类。使用 GetFiles 方法,我们可以编写与第一个慢方法等效的方法。

DateTime GetLastFileModifiedFast(string dir)
{
    DateTime retval = DateTime.MinValue;
    
    FileData [] files = FastDirectoryEnumerator.GetFiles(dir);
    for (int i=0; i<files.Length; i++)
    {
        if (files[i].LastWriteTime > retval)
        {
            retval = lastWriteTime;
        }
    }
    
    return retval;
}

FileData 对象提供了 FileInfo 类提供的文件的所有标准属性。

使其更快

使用 EnumerateFiles 方法的其中一个重载来枚举目录中的所有文件。枚举返回一个 FileData 对象。

以下是使用 FastDirectoryEnumerator 的相同方法的示例

DateTime GetLastFileModifiedFast(string dir)
{
    DateTime retval = DateTime.MinValue;

    foreach (FileData f in FastDirectoryEnumerator.EnumerateFiles(dir))
    {
        if (f.LastWriteTime > retval)
        {
            retval = f.LastWriteTime;
        }
    }

    return retval;
}

性能

测试应用程序允许您在目录中创建大量文件,然后测试使用所有三种方法进行枚举所需的时间。我使用了一个包含 3000 个文件的目录,并对每个测试运行了三次以给出每个测试的最佳答案。

使用本地硬盘驱动器上的路径会导致以下时间

  • Directory.GetFiles 方法:~225 毫秒
  • DirectoryInfo.GetFiles 方法:~230 毫秒
  • FastDirectoryEnumerator.GetFiles 方法:~33 毫秒
  • FastDirectoryEnumerator.EnumerateFiles 方法:~27 毫秒

这大约是速度最快和最慢方法之间性能的 8.5 倍提升。当文件位于 UNC 路径上时,性能提升更加明显。对于此测试,我使用了与之前的测试相同的目录。唯一的区别是,我通过 UNC 共享名称而不是本地路径引用了该目录。在测试时,我连接到我的家庭无线网络。

  • Directory.GetFiles 方法:~43,860 毫秒
  • DirectoryInfo.GetFiles 方法:~44,000 毫秒
  • FastDirectoryEnumerator.GetFiles 方法:~55 毫秒
  • FastDirectoryEnumerator.EnumerateFiles 方法:~53 毫秒

这大约是性能的 830 倍提升,超过了两个数量级!而且,随着包含文件的 PC 的延迟增加,差距只会越来越大。

为什么更快?

如上所述,Directory.GetFilesDirectoryInfo.GetFiles 存在许多缺点。最重要的是它们会丢弃信息,并且不能有效地允许您同时检索有关多个文件的信息。

在内部,Directory.GetFiles 被实现为 Win32 FindFirstFile/FindNextFile 函数的包装器。这些函数都返回有关每个已枚举文件的信息,而 GetFiles() 方法在返回文件名时会丢弃这些信息。它们还使用单个网络消息检索有关多个文件的信息。

FastDirectoryEnumerator 保留了此信息,并在 FileData 类中返回它。这大大减少了完成相同任务所需的网络往返次数。

历史

  • 2009 年 8 月 13 日:初始版本。
  • 2009 年 8 月 14 日:添加了安全检查、参数检查和 GetFiles 方法。
  • 2009 年 8 月 24 日:修复了使用 GetFiles 的 AllDirectories 搜索。删除了有关 .NET 4.0 包含类似内容的说明。
  • 2009 年 9 月 8 日:修复了当过滤器不是 * 或 *.* 时 AllDirectories 搜索的问题。
© . All rights reserved.