Delphi搜索文件和目录的最快算法

7
我正在使用Delphi7,需要解决一个大问题。有没有人能提供比使用findnext和findfirst更快的搜索文件和文件夹的方法?因为我还要处理每个文件/文件夹的数据(创建日期/作者/大小等),这需要很长时间……我在WinApi下搜索了很多但可能没找到最好的函数来完成这个任务。所有我找到的用Delphi编写的示例都使用findfirst和findnext……

另外,我不想购买组件或使用一些免费的。

谢谢您提前的帮助!

10个回答

7
我认为任何你可能购买的组件都会使用findfirst/findnext,当然是递归方式。我认为没有办法在不实际查看每个目录和文件的情况下查看每个目录和文件。
作为一个基准来查看您的代码是否相当快,请与WinDirStat http://windirstat.info/ 的性能进行比较(仅到收集数据并准备构建其空间使用图表的阶段)。如果您想要查看它们正在做什么,源代码也可用。它是C语言编写的,但我希望它使用相同的API调用。

3
也许最好将性能与我网站 http://www.splashsoft.de 上的 RidNacs 进行比较。它是用 Delphi 编写的,使用了 findfirst/findnext 等技术... 它比 WinDirStat 快得多。 :-) - splash
1
@splash:我非常兴奋地发现了RidNacs(它比WinDirStat快得离谱!),但是后来我意识到它不支持Unicode。:( 有没有可能它可以使用Unicode API而不是ANSI? - user541686
@splash,你还在更新RidNacs吗?我喜欢它的工作方式!现在主要问题是字体太小了。 - Edwin Yip

7
您可以做的最大改进是直接解析MFT(主文件表),如果您的卷是NTFS格式的话。这样一来,您可以非常快地枚举文件——我们说的是至少快一个数量级。如果您需要的所有元数据都是MFT记录的一部分,则搜索速度会更快。即使您需要读取更多的元数据,您也能够非常快速地建立候选文件列表。
缺点是您必须自己解析MFT:我不知道是否有WinAPI函数可以执行此操作。您还需要担心Shell通常为您担心的事情,例如硬链接、联接、重分配点、符号链接和Shell链接等。
但是,如果您想要速度,增加复杂性是实现它的唯一方法。

我不知道是否有可用的Delphi代码已经实现了MFT解析器,所以你可能需要使用第三方库或自己实现。我原本想建议使用开源(GPL)NTFS Undelete,它是用Delphi编写的,但是它通过Python代码实现MFT解析,并内置了Delphi-Python桥接。


2
如果您想要获得真正快速的搜索结果,考虑使用Windows搜索(API)或索引服务。
其他改进可能是利用线程并拆分文件搜索和文件属性收集,或者只进行线程搜索。

谢谢您的建议,但是将这个递归任务分成并发线程有点困难... - RBA
1
硬盘上的多线程搜索如果被搜索的卷无法处理所产生的IOPS,性能将会非常糟糕。否则,由于磁盘碎片而导致性能下降,而不是提高。 - afrazier

2
我曾经遇到过一个非常类似的问题,即目录中的文件数量和findfirst/findnext一起使用时所花费的时间比合理时间长。对于少量文件来说,这不是问题,但是当你扩展到数千个或数万个文件时,性能会显著下降。
我们的解决方案是在一个单独的目录中使用队列文件。当文件被“添加”到系统中时,它们将被写入一个队列文件(一个固定的记录文件)。当系统需要处理数据时,它会查看文件是否存在,如果存在,则重命名它并打开重命名版本(这样下一个进程通行证可以发生)。然后按顺序处理该文件。然后,我们将队列文件和处理后的文件归档到一个基于日期和时间的子目录中(例如:G:\PROCESSED\2010\06\25\1400 包含在2010年6月25日下午2:00运行的文件)。
采用这种方法,我们不仅实现了几乎“实时”处理文件(仅受到处理队列文件的频率的延迟影响),而且还确保按添加顺序处理文件。

这是最好的解决方案...但是,程序不会像任务或服务一样安装,第一次扫描后,将挂钩操作系统事件以便将修改“添加”到队列中。这是最好的解决方案,但我受到其他“专业人士”的限制,必须按照某些特定方式进行。我找到的解决方案是:仅对所有目录进行系统解析,创建一个具有索引类型的结构的临时文件。之后,我将解析该结构,获取每个元素的所有文件,并创建另一个临时文件,其中包含索引。 - RBA
从第一个开始,只返回所需信息。谢谢并致以最好的问候。 - RBA

1

我通过使用两个线程解决了类似的问题。这样,我可以在文件从磁盘扫描时“处理”文件,同时进行处理。在我的情况下,处理速度明显比扫描速度慢,因此我还必须限制一次性在内存中的文件数量。

TMyScanThread

扫描文件结构,对于每个“匹配项”,使用Syncronize()将路径+文件添加到TList/TStringList或类似的列表中。请记住在循环内部Sleep(),以让操作系统也有一些时间。

线程的伪代码:

TMyScanThread=class(TThread)
private
  fCount : Cardinal;
  fLastFile : String;
  procedure GetListCount;
  procedure AddToList;  
public
  FileList : TStringList;
  procedure Execute; Override;
end;

procedure TMyScanThread.GetListCount;
begin
  fCount := FileList.Count;
end;

procedure TMyScanThread.AddToList;
begin
  FileList.Add(fLastFile);
end;

procedure TMyScanThread.Execute;
begin
     try
        { Get the list size }
        Syncronize( GetListCount );
        if fCount<500 then
        begin
          // FindFirst code goes here
          { Add a file to the list }
          fLastFile := SR.Name; // Store Filename in local var
          Syncronize( AddToList ); // Call method to add to list
          SleepEx(0,True);
        end else
          SleepEx(1000,True);
     finally
        Terminate;
     end;
end;

TMyProcessFilesThread

获取列表中最旧的条目并处理它。然后将结果输出到 DB。

该类实现了类似于访问列表的同步方法。

Syncronize() 调用的一种替代方法是使用 TCriticalSection。在线程之间实现同步通常是品味和任务的问题...


1
如果您需要扫描具有如此多文件的远程驱动器,我强烈建议使用“客户端-服务器”设计进行扫描,以便实际的文件扫描始终在本地完成,只有结果被远程获取。这将节省大量时间。此外,所有“服务器”都可以并行扫描。

可能会是这样,同时使用RemObjects将信息发送到中央服务器,该服务器将填充数据库。谢谢。 - RBA

1
如果您的程序正在运行在Windows 7或Server 2008 R2上,那么Windows FindFirstFileEx函数有一些增强功能可以使其运行速度更快。您需要复制并修改VCL函数以包含这些新选项。

我知道那个函数,但我被限制使用findfirst。谢谢。 - RBA

1

使用findfirst/findnext循环进行优化的空间并不多,因为这主要是I/O受限的:操作系统需要从硬盘中读取这些信息!

证明:编写一个简单的findfirst/findnext循环程序,对找到的文件什么都不做。重启计算机并在大型目录上运行它,记录完成所需的时间。然后再次运行它,无需重新启动计算机。您会注意到第二次运行速度明显更快,因为操作系统已经缓存了这些信息!

如果您确定正在尝试扫描的目录由于其他应用程序正在使用数据而被操作系统频繁访问(这将使目录结构信息存储在操作系统的缓存中,并且扫描不会受到I/O限制),则可以尝试使用线程并行运行多个findfirst/findnext循环。这样做的缺点是,如果目录结构尚未缓存到操作系统中,则您的算法仍受到HDD的I/O限制,而且可能比原来更糟糕,因为现在您正在进行多个并行的I/O请求,需要由同一设备处理。

当我解决同样的问题时,我选择不使用并行循环,因为应用程序的第二次运行总是要快得多,证明我受到I/O限制,无论如何优化CPU都无法解决I/O瓶颈。


1

0
当我在文件系统中处理大量小文件时遇到性能问题,于是将这些文件作为数据库中的BLOB存储。相关信息如大小、创建时间和作者也可以存储在数据库中。一旦数据表在数据库中得到填充,我怀疑数据库引擎可以更快地查找记录(文件)而不是我们提出的任何解决方案,因为数据库代码专门针对大型数据集进行高效搜索。这样做肯定会更加灵活,因为添加新的搜索只需创建一个新的Select语句即可。例如:Select * from files where author = 'bob' and size > 10000
我不确定这种方法是否能帮助您。您能告诉我们更多关于您如何处理这些文件和搜索条件的信息吗?

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接