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

时间:2010-01-13 20:15:54

标签: python indexing directory performance all-files

尝试#2:

人们似乎并不理解我想要做的事情。让我看看我是否可以更明确地说明:

1)读取文件列表比走一个目录要快得多。

2)所以让我们有一个遍历目录并将结果列表写入文件的函数。现在,在将来,如果我们想要获取该目录中的所有文件,我们就可以读取此文件而不是遍历目录。我把这个文件称为索引。

3)显然,随着文件系统的更改,索引文件变得不同步。为了解决这个问题,我们有一个独立的程序挂钩到操作系统,以监视文件系统的变化。它将这些更改写入称为监视器日志的文件。在我们读取特定目录的索引文件之后,我们立即使用监视器日志将各种更改应用于索引,以便它反映目录的当前状态。

因为阅读文件比走一个目录要便宜得多,所以这比在第一次调用之后的所有通话步行要快得多。

原帖:

我想要一个递归获取任何给定目录中所有文件的函数,并根据各种参数对其进行过滤。而且我希望它快速 - 就像比简单地走dir快一个数量级。而我更喜欢用Python来做。跨平台是首选,但Windows是最重要的。

以下是关于如何解决这个问题的想法:

我有一个名为all_files的函数:

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

第一次调用此函数时,它将使用os.walk构建所有文件的列表,以及有关文件的信息,例如它们是否隐藏,符号链接等。我将写入此数据到目录中名为“.index”的文件。在对all_files的后续调用中,将检测.index文件,我将读取该文件而不是遍历目录。

这使得索引在添加和删除文件时不同步的问题。为此,我将在启动时运行第二个程序,检测对整个文件系统的所有更改,并将它们写入名为“mod_log.txt”的文件中。它通过Windows信号检测更改,如here所述的方法。此文件每行包含一个事件,每个事件由受影响的路径,事件类型(创建,删除等)和时间戳组成。 .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 个答案:

答案 0 :(得分:7)

不要尝试复制文件系统已经完成的工作。你不会比现在做得更好。

您的方案在很多方面存在缺陷,并且不会使您获得数量级的改进。

缺陷和潜在问题:

您将始终使用文件系统的快照。你永远不会确定它与现实没有明显的脱节。如果这是在你的应用程序的工作参数范围内,没有汗水。

文件系统监控程序仍然必须以递归方式遍历文件系统,因此工作仍在进行中。

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

您的客户端应用程序可能无法在文件系统监视程序更新索引文件时读取索引文件,因此在客户端等待索引可读时您将浪费时间。

我可以继续。

事实上,如果您不关心使用可能与现实非常不相关的文件系统快照,我认为您最好将索引保留在内存中并使用申请本身。这将清除否则会出现的任何文件争用问题。

答案 1 :(得分:3)

最佳答案来自Michał Marczyk,位于初始问题评论列表的底部。他指出我所描述的非常接近UNIX定位程序。我在这里找到了一个Windows版本:http://locate32.net/index.php。它解决了我的问题。

编辑:实际上Everything搜索引擎看起来更好。显然,Windows会保留文件系统更改的日志,而Everything使用它来使数据库保持最新。

答案 2 :(得分:2)

Windows桌面搜索是否提供此类索引作为副产品?在mac上,可以查询聚光灯索引的文件名,如下所示:mdfind -onlyin . -name '*'

当然,它比走在目录上要快得多。

答案 3 :(得分:1)

简短的回答是“不”。您将无法在Python中构建一个索引系统,该系统将超过文件系统一个数量级。

“索引”文件系统是一项密集/慢速任务,无论缓存实现如何。避免构建文件系统索引的巨大开销的唯一现实方法是“随时进行索引”以避免大的遍历。 (毕竟,文件系统本身已经是数据索引器。)

有些操作系统功能可以执行“随时构建”文件系统索引。它是Spotlight on OSX和Windows桌面搜索等服务的基础。

为了获得比走过目录更快的速度,你需要利用其中一个操作系统或文件系统级工具。

另外,尽量不要误导自己认为解决方案更快,因为你已经将工作“移动”到不同的时间/过程。您的示例代码正是如此。在构建相同文件并创建索引时遍历示例文件的目录结构,然后稍后只读取该文件。

这里有两节课。 (a)要创建适当的测试,必须将“设置”与“测试”分开。在这里,您的性能测试基本上说,“哪个更快,遍历目录结构或读取已经预先创建的索引?”显然,这不是苹果与橘子的比较。

然而,(b)你在同一时间偶然发现了正确的答案。如果使用现有索引,则可以更快地获取文件列表。这是您需要利用Windows桌面搜索或Spotlight索引等内容的地方。

毫无疑问,为了构建文件系统的索引,根据定义,您必须“访问”每个文件。如果您的文件存储在树中,那么递归遍历可能是您访问每个文件的最快方式。如果问题是“我可以编写Python代码来完成os.walk所做的事情,但要比os.walk快一个数量级”,那么答案就是响亮的 no 。如果问题是“我可以编写Python代码来索引系统上的每个文件而不花时间实际访问每个文件”,那么答案仍然是 no

编辑以回应“我不认为你理解我正在做的事情”

让我们在这里清楚,几乎每个人都明白你要做的事情。你似乎正在接受“不,这不会像你希望它一样工作”意味着我们不理解。

让我们从另一个角度来看待这个问题。文件系统从一开始就是现代计算的重要组成部分。数据的分类,索引,存储和检索是计算机科学和计算机工程的重要组成部分,计算机科学中许多最杰出的人都在不断地研究它。

您希望能够根据文件的属性/元数据/数据过滤/选择文件。这是在计算中不断使用的极其常见的任务。即使在你正在使用的计算机上,它也可能每秒发生几次。

如果通过简单地保留文件名和属性的文本文件索引来简化这个过程一个数量级(!)就好了,你不觉得每个文件系统和操作系统都在存在会做到这一点吗?

这就是说,当然缓存特定查询的结果可以为您带来一些小的性能提升。而且,正如预期的那样,文件系统和磁盘缓存是每个现代操作系统和文件系统的基本组成部分。

但是,正如您所问,您的问题有一个明确的答案:。在一般情况下,重新实现os.walk的速度不会快一个数量级。您可以通过缓存获得更好的分期运行时间,但如果您正确地包含在分析中构建缓存的工作,则不会超过它的数量级。

答案 4 :(得分:0)

我建议你只使用os.walk(获取目录树)和&组合。 os.stat(获取文件信息)。使用std-lib将确保它适用于所有平台,并且它们可以很好地完成工作。而且无需索引任何内容。

正如其他人所说,我并不认为你会通过尝试索引和重新索引文件系统来购买太多东西,特别是如果你已经通过路径和参数限制了你的功能。

答案 5 :(得分:0)

我是Python的新手,但我正在使用列表推导,迭代器和生成器的组合应该根据我读过的报告尖叫。

class DirectoryIterator:
    def __init__(self, start_dir, pattern):
        self.directory = start_dir
        self.pattern = pattern

 def __iter__(self):
     [([DirectoryIterator(dir, self.pattern) for dir in dirnames], [(yield os.path.join(dirpath, name)) for name in filenames if re.search(self.pattern, name) ]) for dirpath, dirnames, filenames in os.walk(self.directory)]

 ###########

 for file_name in DirectoryIterator(".", "\.py$"): print file_name