使用索引快速递归获取目录中的所有文件

2 投票
6 回答
5623 浏览
提问于 2025-04-15 18:00

尝试 #2:

大家似乎不太明白我想做什么。让我试着更清楚地说明一下:

1) 读取文件列表比逐个遍历文件夹要快得多。

2) 所以我们可以写一个函数,遍历一个文件夹,并把得到的文件列表写入一个文件。将来如果我们想获取这个文件夹里的所有文件,就可以直接读取这个文件,而不是再去遍历文件夹。我把这个文件叫做索引。

3) 显然,随着文件系统的变化,索引文件会变得不准确。为了应对这个问题,我们有一个单独的程序,它可以监控操作系统的变化,记录文件系统的变动。它把这些变化写入一个叫做监控日志的文件中。在我们读取某个文件夹的索引文件后,立刻用监控日志来更新索引,以反映当前文件夹的状态。

因为读取文件比遍历文件夹便宜得多,所以在第一次之后,所有的调用应该会快很多。

原始帖子:

我想要一个函数,可以递归地获取任何给定文件夹中的所有文件,并根据各种参数进行过滤。我希望它能快一点——比简单遍历文件夹快一个数量级。而且我希望用Python来实现。最好是跨平台,但Windows最重要。

这是我打算怎么做的:

我有一个叫做all_files的函数:

def all_files(dir_path, ...parms...):
    ...

第一次调用这个函数时,它会使用os.walk来构建一个所有文件的列表,并附带文件的信息,比如它们是否是隐藏文件、符号链接等。我会把这些数据写入一个叫“.index”的文件中。在后续调用all_files时,会检测到.index文件,我就会读取这个文件,而不是再去遍历文件夹。

这就留下了一个问题:随着文件的添加和删除,索引可能会变得不准确。为了解决这个问题,我会有一个第二个程序,它在启动时运行,检测整个文件系统的所有变化,并把它们写入一个叫“mod_log.txt”的文件。它通过Windows信号来检测变化,就像这里描述的方法一样这里。这个文件每行包含一个事件,每个事件包括受影响的路径、事件类型(创建、删除等)和时间戳。 .index文件也会有一个时间戳,表示它最后一次更新的时间。在all_files中读取.index文件后,我会查看mod_log.txt,找到在.index文件时间戳之后发生的事件。然后我会处理这些最近的事件,找出适用于当前文件夹的,并相应地更新.index。

最后,我会把所有文件的列表进行过滤,并返回结果。

你觉得我的方法怎么样?有没有更好的办法?

编辑:

看看这段代码。我发现从读取缓存列表中获得的速度提升非常明显,比递归遍历快多了。

import os
from os.path import join, exists
import cProfile, pstats

dir_name = "temp_dir"
index_path = ".index"

def create_test_files():
    os.mkdir(dir_name)
    index_file = open(index_path, 'w')
    for i in range(10):
        print "creating dir: ", i
        sub_dir = join(dir_name, str(i))
        os.mkdir(sub_dir)
        for i in range(100):
            file_path = join(sub_dir, str(i))
            open(file_path, 'w').close() 
            index_file.write(file_path + "\n")
    index_file.close()
#

#  0.238 seconds
def test_walk():            
    for info in os.walk("temp_dir"):
        pass

#  0.001 seconds
def test_read():
    open(index_path).readlines()

if not exists("temp_dir"):
    create_test_files()

def profile(s):
    cProfile.run(s, 'profile_results.txt')
    p = pstats.Stats('profile_results.txt')
    p.strip_dirs().sort_stats('cumulative').print_stats(10)

profile("test_walk()")
profile("test_read()")

6 个回答

2

难道Windows桌面搜索不会提供这样的索引吗?在Mac上,Spotlight索引可以用来查询文件名,比如这样:mdfind -onlyin . -name '*'

当然,这样查询比一个一个地浏览文件夹要快得多。

7

别试图重复文件系统已经完成的工作。你做得不会比它更好。

你的方案有很多缺陷,根本无法带来显著的改进。

缺陷和潜在问题:

你总是只能使用文件系统的快照。你永远无法确定这个快照和实际情况有多大差距。如果这在你的应用程序的工作范围内,那就没问题。

文件系统监控程序仍然需要遍历整个文件系统,所以这项工作依然在进行。

为了提高缓存的准确性,你必须增加文件系统监控程序的运行频率。它运行得越频繁,你节省的实际时间就越少。

你的客户端应用可能无法在文件系统监控程序更新索引文件时读取这个索引,所以客户端会在等待索引可读时浪费时间。

我可以继续说下去。

如果你真的不在乎使用一个可能与实际情况差距很大的文件系统快照,我觉得你更应该把索引保存在内存中,并由应用程序本身来更新。这样可以避免可能出现的文件争用问题。

3

最好的回答来自于Michał Marczyk,他在最初问题的评论列表底部提到的。他指出我所描述的内容和UNIX系统中的locate程序非常相似。我在这里找到了一个Windows版本:http://locate32.net/index.php。这个工具解决了我的问题。

补充一下:其实Everything搜索引擎看起来更好。显然,Windows会记录文件系统的变化,而Everything利用这些记录来保持数据库的最新状态。

撰写回答