Эффективно трассировать дерево каталогов с помощью opendir(), readdir() и closedir()
Подпрограммы C opendir(), readdir() и closedir() предоставляют мне возможность пересекать структуру каталогов. Тем не менее, каждая структура dirent, возвращаемая readdir(), по-видимому, не дает мне полезного способа получить набор указателей на DIR, которые мне нужно будет переписать в подкаталоги каталога.
Конечно, они дают мне имя файлов, поэтому я могу либо добавить это имя в путь к каталогу, либо в stat() и opendir(), либо я могу изменить текущий рабочий каталог процесса через chdir ( ) и отбросить его обратно через chdir ( ".." ).
Проблема с первым подходом заключается в том, что если длина пути к каталогу достаточно велика, то стоимость передачи строки, содержащей ее в opendir(), приведет к избыточному весу стоимости открытия каталога. Если вы немного более теоретичны, вы можете сказать, что ваша сложность может увеличиться за пределы линейного времени (в общем количестве символов (относительных) имен файлов в дереве каталогов).
Кроме того, второй подход имеет проблему. Поскольку каждый процесс имеет один текущий рабочий каталог, все, кроме одного потока, должны будут блокироваться в многопоточном приложении. Кроме того, я не знаю, является ли текущий рабочий каталог просто удобством (т.е. Относительный путь будет добавлен к нему до запроса файловой системы). Если это так, этот подход также будет неэффективным.
Я принимаю альтернативы этим функциям. Итак, как эффективно обрабатывать дерево каталогов UNIX эффективно (линейное время в общем количестве символов файлов под ним)?
Ответы
Ответ 1
Вы пробовали ftw()
aka Прохождение файла дерева?
Снипп из man 3 ftw
:
int ftw(const char *dir, int (*fn)(const char *file, const struct stat *sb, int flag), int nopenfd);
ftw() проходит через дерево каталогов, начиная с указанного каталога каталога. Для каждой найденной записи в дереве она вызывает fn() с полным именем пути, указателем на структуру stat (2) для записи и флагом int
Ответ 2
Кажется, у вас отсутствует одна базовая точка: обход каталога включает чтение данных с диска. Даже когда/если эти данные находятся в кеше, вы получаете достаточное количество кода, чтобы получить его из кеша в ваш процесс. Пути также в целом довольно короткие - не более пары сотен байт довольно необычно. Вместе это означает, что вы можете довольно разумно создавать строки для всех путей, которые вам нужны, без реальной проблемы. Время, затрачиваемое на построение строк, по-прежнему довольно незначительно по сравнению со временем для чтения данных с диска. Это означает, что вы обычно можете игнорировать время, затрачиваемое на манипуляции с строкой, и работать исключительно над оптимизацией использования диска.
Мой собственный опыт состоял в том, что для большинства обращений к каталогам поиск по ширине обычно предпочтительнее - когда вы проходите текущий каталог, поместите все пути во все подкаталоги в нечто вроде очереди приоритетов. Когда вы закончите перемещение текущего каталога, вытащите первый элемент из очереди и пройдете его, продолжая, пока очередь не будет пустой. Обычно это улучшает локальность кэша, поэтому сокращается время, затрачиваемое на чтение диска. В зависимости от системы (скорость диска и скорость процессора, общая доступная память и т.д.) Она почти всегда не меньше, чем при первом прохождении по глубине, и может быть легко в два раза быстрее (или так).
Ответ 3
Способ использования opendir
/readdir
/closedir
заключается в том, чтобы сделать функцию рекурсивной! Взгляните на фрагмент здесь, на Dreamincode.net.
Надеюсь, что это поможет.
РЕДАКТИРОВАТЬ Спасибо R.Sahu, истекающий срок ссылки, однако, нашел его через обратный архив и взял свободу добавить его в gist. Пожалуйста, помните, чтобы проверить лицензию соответствующим образом и приписать оригинал автора для источника!:)
Ответ 4
Вероятно, избыток для вашего приложения, но здесь библиотека, предназначенная для перемещения дерева каталогов с сотнями миллионов файлов.
https://github.com/hpc/libcircle
Ответ 5
Вместо opendir()
вы можете использовать комбинацию openat()
, dirfd()
и fdopendir()
и создать рекурсивную функцию для fdopendir()
дерева каталогов:
#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()
все еще используется для получения следующей записи каталога. Если следующая запись является каталогом, то мы находим родительский каталог fd с помощью dirfd()
и передаем его вместе с именем дочернего каталога в openat()
. Полученный fd ссылается на дочерний каталог. Это передается в fdopendir()
которая возвращает указатель DIR *
для дочернего каталога, который затем может быть передан нашему dir_recurse()
где он снова будет действительным для использования с readdir()
.
Эта программа повторяется по всему дереву каталогов с корнем в .
, Записи печатаются с отступом в 1 пробел на уровень каталога. Каталоги печатаются с помощью лидирующего /
.
На идеоне.