【问题标题】:Efficiently Traverse Directory Tree with opendir(), readdir() and closedir()使用 opendir()、readdir() 和 closedir() 有效地遍历目录树
【发布时间】:2010-02-22 15:57:39
【问题描述】:

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

当然,他们给了我文件的名称,所以我可以将该名称附加到目录路径和 stat() 和 opendir() 它们,或者我可以通过 chdir( ) 并通过 chdir("..") 回滚。

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

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

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

【问题讨论】:

  • 文件名或子目录的最大长度由 MAXCOMPLEN 设置,传统上是 255,几乎不会超过 512。因此,如果你让你的函数递归,你肯定不会有很大的字符串分配和管理包含目录路径的字符串会影响遍历的整体复杂性。

标签: c filesystems readdir traversal closedir


【解决方案1】:

您尝试过ftw() aka File Tree Walk 吗?

来自man 3 ftw的片段:

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

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

【讨论】:

  • nftw() 有时-两者之间存在细微差别,但我必须手动查找它...opengroup.org/onlinepubs/9699919799/functions/nftw.html ("nftw() 函数将递归下降以路径为根的目录层次结构。nftw() 函数与 ftw() 具有类似的效果,只是它需要一个额外的参数 flags...")。
  • 感谢您提醒我nftw()。我确实记得在ftw() 上使用它,因为前者允许您传递一个标志来告诉它不要递归符号链接(除其他外)。
  • 我们可以使用 ftw() 来按顺序遍历目录吗? (这样我们就可以从目录树结构的底部向上删除文件/目录)
  • @D-Shan 是的。请参阅手册页中的 FTW_DEPTH 标志:linux.die.net/man/3/nftw
【解决方案2】:

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

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

【讨论】:

  • 为什么要使用优先级队列而不是像 FIFO 队列这样简单的东西?你用什么作为优先属性?
  • @Andrew:好问题。 FIFO 可以很好地工作。 PQ 可以很容易地按照用户通常喜欢的名称排序生成结果(当然,我在使用它时更喜欢它......)
  • 谢谢,有道理,我没有考虑输出格式。
【解决方案3】:

opendir/readdir/closedir的使用方式是让函数递归!在Dreamincode.net 上查看 sn-p。

希望这会有所帮助。

编辑感谢 R.Sahu,链接已过期,但是,通过 wayback archive 找到它并冒昧地将其添加到 gist。请记住,相应地检查许可证并注明出处为原作者! :)

【讨论】:

  • 代码链接不再有效。仅供参考。
【解决方案4】:

可能对您的应用程序来说有点过头了,但这里有一个库,旨在遍历包含数亿个文件的目录树。

https://github.com/hpc/libcircle

【讨论】:

    【解决方案5】:

    您可以使用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.

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-02-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-06-22
      • 1970-01-01
      • 2016-06-21
      • 1970-01-01
      相关资源
      最近更新 更多