Алгоритм обхода дерева для структур каталогов с большим количеством файлов

при рекурсивном прохождении через структуру каталогов, какой наиболее эффективный алгоритм использовать, если у вас больше файлов, чем каталогов? Я замечаю, что при использовании первого обхода глубины, кажется, требуется больше времени, когда в данном каталоге много файлов. Работает ли в этом случае широтно-первый обход более эффективно? У меня нет возможности профилировать два алгоритма на данный момент, поэтому ваши идеи очень приветствуются.

EDIT: в ответ на alphazero комментарий, Я использую PHP на машине Linux.

5 ответов


имеет смысл, что ширина-сначала будет работать лучше. При вводе корневой папки создается список элементов, с которыми необходимо иметь дело. Некоторые из этих элементов являются файлами, а некоторые-каталогами.

Если вы используете width-first, вы будете иметь дело с файлами в каталоге и забыть о них, прежде чем перейти к одному из дочерних каталогов.

Если вы используете глубину-во-первых, вам нужно продолжать расти список файлов, чтобы иметь дело с Позже, как вы детализировать глубже. Этот будет использовать больше памяти для поддержания списка файлов для работы, возможно, вызывая больше ошибок страницы и т. д...

кроме того, вам все равно нужно будет просмотреть список новых элементов, чтобы выяснить, какие из них являются каталогами, в которые вы можете сверлить. Вам нужно будет снова пройти через тот же список (за вычетом каталогов), когда Вы дойдете до точки работы с файлами.


поскольку у вас больше файлов, чем каталогов, не похоже, что вы имеете дело с очень глубоко вложенными каталогами, которые заставят DFS занять больше памяти (и, следовательно, несколько больше времени), чем BFS. По сути, BFS и DFS делают одно и то же (т. е. посещают каждый узел графика), и поэтому в целом их скорости не должны отличаться на какую-либо значительную величину.

трудно сказать, почему именно ваш DFS медленнее, фактически не видя вашей реализации. Вы уверены, что вы не посещаете одни и те же узлы более одного раза из-за ссылок/ярлыков в вашей файловой системе? Вы также, вероятно, получите значительное ускорение, если вы используете явный DFS на основе стека, а не рекурсию.


вы, вероятно, хотите сканировать содержимое каталога только один раз в каталоге, поэтому обработка заказов - обрабатываете ли вы содержимое каталога до или после посещения других каталогов, вероятно, имеет большее значение, чем то, выполняете ли вы поиск по глубине или по ширине. В зависимости от вашей файловой системы также может быть более эффективным обрабатывать файловые узлы раньше, чем stating их, чтобы увидеть, являются ли они файлами или каталогами. Поэтому я бы предложил pre-order depth-первый поиск в качестве отправной точки, как самый простой в реализации и, скорее всего, иметь хорошую производительность кэша/поиска.

in summary-pre-order depth-first-при входе в каталог, перечислите его содержимое, обработайте любые файлы в этом каталоге и сохраните список имен дочерних каталогов. Затем введите каждый дочерний каталог по очереди. Просто используйте стек вызовов программы как стек, если вы не знаете, что у вас есть очень глубокие структуры каталогов.


структура каталогов Travse с использованием BFS (как упомянул Игорь).

когда вы достигнете каталога, запустите поток, чтобы перечислить все файлы в каталоге.

и убить поток после завершения листинга/файлы travseing.

таким образом, для каждого каталога будет отдельный поток для списка файлов.

пример:

root

  - d1
    - d1.1
    - d1.2
    - f1.1 ... f1.100

  - d2
    - d2.1
    - d2.2
    - d2.3
    - f2.1 ... f2.200

  - d3 
    ....

выход может выглядеть так ->

 got d1

   started thread to get files of d1

   got d2

   started thread to get files of d1

   done with files in d1

   got d3

   started thread to get files of d1

   got d1.1
   started thread to get files of d1.1

   got d1.2
   started thread to get files of d1.2

Итак, к тому времени, когда вы вернетесь в travse глубины каталога поток для получения файлов завершил бы(почти) свою работу.

надеюсь, это полезно.


Это было бы наиболее эффективным в Windows (класс DirectoryTreeReader), он использует дыхание первым и хранит каждый каталог.

static const uint64 DIRECTORY_INDICATOR = -1;//std::numeric_limits <uint64>::max();

class DirectoryContent {

public:
    DirectoryContent(const CString& path)
    : mIndex(-1) 
    {
        CFileFind finder;
        finder.FindFile(path + L"\*.*");
        BOOL keepGoing = FALSE;
        do {
            keepGoing = finder.FindNextFileW();
            if (finder.IsDots()) {
                // Do nothing...
            } else if (finder.IsDirectory()) {
                mPaths.push_back(finder.GetFilePath());
                mSizes.push_back(DIRECTORY_INDICATOR);
            } else {
                mPaths.push_back(finder.GetFilePath());
                mSizes.push_back(finder.GetLength());
            }
        } while(keepGoing);
    }

    bool OutOfRange() const {
        return mIndex >= mPaths.size();
    }
    void Advance() {
        ++mIndex;
    }
    bool IsDirectory() const {
        return mSizes[mIndex] == DIRECTORY_INDICATOR;
    }
    const CString& GetPath() const {
        return mPaths[mIndex];
    }
    uint64 GetSize() const {
        return mSizes[mIndex];
    }

private:
    CStrings mPaths;
    std::vector <uint64> mSizes;
    size_t mIndex;
};

class DirectoryTreeReader{
    DirectoryTreeReader& operator=(const DirectoryTreeReaderRealtime& other) {};
    DirectoryTreeReader(const DirectoryTreeReaderRealtime& other) {};

public:
    DirectoryTreeReader(const CString& startPath)
    : mStartPath(startPath){
        Reset();
    }

    void Reset() {
        // Argh!, no clear() in std::stack
        while(!mDirectoryContents.empty()) {
            mDirectoryContents.pop(); 
        }
        mDirectoryContents.push( DirectoryContent(mStartPath) );
        Advance();
    }
    void Advance() {
        bool keepGoing = true;
        while(keepGoing) {
            if (mDirectoryContents.empty()){
                return;
            }
            mDirectoryContents.top().Advance();
            if (mDirectoryContents.top().OutOfRange()){
                mDirectoryContents.pop();
            } else if ( mDirectoryContents.top().IsDirectory() ){
                mDirectoryContents.push( DirectoryContent(mDirectoryContents.top().GetPath()) );
            } else {
                keepGoing = false;
            }
        }
    }
    bool OutOfRange() const {
        return mDirectoryContents.empty();
    }
    const CString& GetPath() const {
        return mDirectoryContents.top().GetPath();
    }
    uint64 GetSize() const {
        return mDirectoryContents.top().GetSize();
    }

private:
    const CString mStartPath;
    std::stack <DirectoryContent> mDirectoryContents;
};