在文件夹中迭代遍历大量文件

19

在NTFS和Windows 7下,当目录中的文件数大于2,500,000个时,迭代遍历所有文件的最快方式是什么?所有的文件都位于顶层目录下。

目前我使用

for root, subFolders, files in os.walk(rootdir):
    for file in files:
        f = os.path.join(root,file)
        with open(f) as cf:
            [...]

但是这个进程非常非常慢。该进程已运行约一个小时,仍未处理任何文件,但内存使用量每秒仍增加约2KB。


1
重构文件系统是一种选择吗?将一个拥有大量文件的文件夹重构为许多包含一些文件的文件夹。 - thejh
是的,那是一个好主意。但在这里迭代和移动文件也需要很长时间吧? - reox
2
1000000000个文件放在一个平面目录中还是放在目录树中?如果是后者,树的深度是多少? - Sylvain Leroux
针对内存问题,尝试通过生成器(用于惰性求值)交换“for file in files”的方式: "gen = (filex for filex in files); for file in gen: etc." - lucasg
@georges 使用生成器在这里绝对是必须的。但我认为主要问题在于os.walk试图返回目录中所有文件的列表。在这种特殊情况下,我们必须找到另一种方法逐个获取文件名。 glog.iglob是这样一个函数,因为它返回一个迭代器。我已相应地修改了下面的答案。我很好奇看看它是否能简化事情... - Sylvain Leroux
显示剩余4条评论
3个回答

10

默认情况下,os.walk 自底向上遍历目录树。如果你的目录树很深且有许多叶子节点,我猜这可能会导致性能下降--或至少会增加 "启动" 时间,因为在处理第一个文件之前,walk 必须读取大量数据。

所有这些都是推测,你尝试过强制进行自顶向下的探索吗:

for root, subFolders, files in os.walk(rootdir, topdown=True):
    ...

编辑:

由于文件似乎在一个平面目录中,也许 glob.iglob 可以通过返回迭代器来提高性能(而其他方法如 os.walkos.listdirglob.glob 首先构建所有文件的列表)。你可以尝试这样做:

import glob

# ...
for infile in glob.iglob( os.path.join(rootdir, '*.*') ):
    # ...

2
我发现所有这些功能都很糟糕,直到文件系统建立了其索引文件。在Windows创建目录的B树之后(当您首次迭代结构时完成),一切都可以在几秒钟内启动。 - reox
非常有趣。但是关于基于glob.iglob的答案有点令人失望。我对Windows不是很熟悉,所以我不太理解。你是如何解决这个问题的?通过手动将“资源管理器”指向目录吗?还是索引在Python程序第一次运行时自动重建了呢? - Sylvain Leroux
似乎Windows在这一点上有些奇怪...当B树没有建立时,您将无法获得迭代器或类似的东西。如果存在这样的树,则会立即收到迭代器并可以继续进行。但是,我在移动一些文件后某种方式导致树被删除了 - 这很奇怪,因为文档告诉我应该重建它。也许如果它太大,则不会重建。 - reox
嗨,@SylvainLeroux,你能看一下这个问题吗https://stackoverflow.com/q/53719293/7644562。谢谢! - Abdul Rehman

9
我发现自从 Python 3.5 起,标准库中的 os.scandir(注意:根据评论,在 MacOS 上也同样表现良好)似乎在 Windows 平台上也能胜任这项工作!考虑以下示例:
"从包含数百万个文件的文件夹中检索100个路径"
使用 os.scandir 可以在短短几秒钟内完成此任务。
import os
from itertools import islice
from pathlib import Path
path = Path("path to a folder with a lot of files")

paths = [i.path for i in islice(os.scandir(path), 100))]

所有其他测试过的选项(iterdir,glob,iglob)似乎需要花费荒谬的时间,尽管它们应该返回迭代器...

paths = list(islice(path.iterdir(), 100))

paths = list(islice(path.rglob(""), 100))

import glob
paths = list(islice(glob.iglob(str(path / "*.*")), 100))

也适用于Mac。这为我节省了很多时间(等待列表)和痛苦 :) 文件数量为1062996,且还在增长... - Jako

2
我使用了类似以下的东西:
from os import scandir
from os.path import isfile, join, exists
import os

def get_files(path):
    if exists(path):
        for file in scandir(path):
            full_path = join(path, file.name)
            if isfile(full_path):
                yield full_path
    else:
        print('Path doesn\'t exist')

def get_subdirs(path):
    if exists(path):
        for subdir in scandir(path):
            full_path = join(path, subdir.name)
            if not isfile(full_path):
                yield full_path
    else:
        print('Path doesn\'t exist')

def walk_dir(directory):
    yield from get_files(directory)
    for subdir in get_subdirs(directory):
        yield from walk_dir(subdir)

walk_dir 方法返回一个生成器对象,可用于遍历文件系统。在递归过程的任何步骤中,不会创建目录列表,因此内存永远不会保存任何子目录中所有文件的列表。


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