使用opendir(),readdir()和closedir()高效遍历目录树

时间:2010-02-22 15:57:39

标签: c filesystems readdir traversal closedir

C例程opendir(),readdir()和closedir()为我提供了遍历目录结构的方法。但是,readdir()返回的每个dirent结构似乎都没有为我提供一个有用的方法来获取我需要递归到目录子目录的DIR指针集。

当然,他们给我文件的名称,所以我可以将该名称附加到目录路径和stat()和opendir()它们,或者我可以通过chdir更改进程的当前工作目录( )并通过chdir(“..”)回滚。

第一种方法的问题是,如果目录路径的长度足够大,那么将包含它的字符串传递给opendir()的成本将超过打开目录的成本。如果你有点理论上的话,可以说你的复杂性可能超过线性时间(在目录树中(相对)文件名的总字符数)。

此外,第二种方法存在问题。由于每个进程都有一个当前工作目录,因此除了一个线程之外的所有进程都必须在多线程应用程序中进行阻塞。另外,我不知道当前工作目录是否仅仅是方便(即,在文件系统查询之前将相对路径附加到它)。如果是这样,这种方法也会效率低下。

我接受这些功能的替代方案。那么如何有效地遍历UNIX目录树(它下面文件的总字符数的线性时间)?

5 个答案:

答案 0 :(得分:15)

您是否尝试过ftw()又名文件树漫步

来自man 3 ftw

的Snippit

int ftw(const char *dir, int (*fn)(const char *file, const struct stat *sb, int flag), int nopenfd);

  

ftw()从指示的目录dir开始遍历目录树。对于树中的每个找到的条目,它使用条目的完整路径名调用fn(),指向条目的stat(2)结构的指针和int标志

答案 1 :(得分:5)

您似乎缺少一个基本点:目录遍历涉及从磁盘读取数据。即使/如果该数据在缓存中,您最终也会通过相当数量的代码将缓存中的数据导入您的流程。路径通常也很短 - 任何超过几百个字节都是非常不寻常的。这些意味着您可以非常合理地为所需的所有路径构建字符串,而不会出现任何实际问题。与从磁盘读取数据的时间相比,构建字符串所花费的时间仍然很少。这意味着您通常可以忽略在字符串操作上花费的时间,并专门用于优化磁盘使用。

我自己的经验是,对于大多数目录遍历而言,广度优先搜索通常更可取 - 当您遍历当前目录时,将所有子目录的完整路径放在类似优先级队列的内容中。遍历当前目录后,从队列中拉出第一个项目并遍历它,继续直到队列为空。这通常可以改善缓存局部性,因此可以减少读取磁盘所花费的时间。它取决于系统(磁盘速度与CPU速度,可用总内存等),它几乎总是至少与深度优先遍历一样快,并且可以轻松地达到两倍(或左右)。

答案 2 :(得分:4)

使用opendir / readdir / closedir的方法是使函数递归!请查看Dreamincode.net上的代码段。

希望这有帮助。

编辑 感谢R.Sahu,已经过期了,但通过wayback archive找到了它,并冒昧地将其添加到{{3} }。请记住,相应地检查许可证并将原始作者归因于源! :)

答案 3 :(得分:2)

您的应用程序可能有点过分,但这里有一个用于遍历包含数亿个文件的目录树的库。

https://github.com/hpc/libcircle

答案 4 :(得分:0)

您可以使用openat()dirfd()fdopendir()的组合来代替opendir(),并构造一个递归函数来遍历目录树:

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <dirent.h>

void
dir_recurse (DIR *parent, int level)
{
    struct dirent *ent;
    DIR *child;
    int fd;

    while ((ent = readdir(parent)) != NULL) {
        if ((strcmp(ent->d_name, ".") == 0) ||
            (strcmp(ent->d_name, "..") == 0)) {
            continue;
        }
        if (ent->d_type == DT_DIR) {
            printf("%*s%s/\n", level, "", ent->d_name);
            fd = openat(dirfd(parent), ent->d_name, O_RDONLY | O_DIRECTORY);
            if (fd != -1) {
                child = fdopendir(fd);
                dir_recurse(child, level + 1);
                closedir(child);
            } else {
                perror("open");
            }
        } else {
            printf("%*s%s\n", level, "", ent->d_name);
        }
    }
}

int
main (int argc, char *argv)
{
    DIR *root;

    root = opendir(".");
    dir_recurse(root, 0);
    closedir(root);

    return 0;
}

此处readdir()仍用于获取下一个目录条目。如果下一个条目是目录,则我们找到带有dirfd()的父目录fd并将其以及子目录名传递给openat()。结果fd引用子目录。这将传递给fdopendir(),该返回一个指向子目录的DIR *指针,然后可以将其传递给我们的dir_recurse(),在此处它再次可用于readdir()调用

该程序遍历以.为根的整个目录树。打印条目,每个目录级别缩进1个空格。目录印有尾随/

On ideone