Введение
Поиск всех файлов с определённым расширением в дереве каталогов, подсчёт суммарного размера логов, построение индекса медиатеки — все эти задачи требуют эффективного обхода файловой системы. QDir::entryList() подходит для одного уровня, но для рекурсивного обхода больших деревьев каталогов оптимальным инструментом в Qt является QDirIterator.
Концепция
QDirIterator — это итератор, который последовательно обходит содержимое директории, не загружая сразу весь список в память (в отличие от QDir::entryInfoList(), который сначала формирует полный список, а потом возвращает его). Это особенно важно при работе с большими деревьями файлов — счёт может идти на сотни тысяч записей.
Флаг QDirIterator::Subdirectories включает рекурсивный обход, а фильтры QDir::Filters (например, QDir::Files, QDir::NoDotAndDotDot) и маски имён (QStringList с шаблонами типа "*.cpp") позволяют точно настроить, что именно требуется найти.
Пример кода: Поиск файлов по
#include <QDirIterator>
#include <QDebug>
#include <QFileInfo>
#include <QElapsedTimer>
// Рекурсивный поиск всех файлов с заданными расширениями
QStringList findFilesByExtension(const QString &rootPath, const QStringList &nameFilters)
{
QStringList result;
QDirIterator it(rootPath, nameFilters,
QDir::Files | QDir::NoSymLinks,
QDirIterator::Subdirectories);
while (it.hasNext()) {
result.append(it.next());
}
return result;
}
// Подсчёт суммарного размера всех файлов в дереве, с фильтрацией по дате
qint64 calculateTotalSize(const QString &rootPath, const QDateTime &modifiedAfter)
{
qint64 totalSize = 0;
int fileCount = 0;
QDirIterator it(rootPath, QDir::Files | QDir::NoSymLinks, QDirIterator::Subdirectories);
while (it.hasNext()) {
it.next();
QFileInfo info = it.fileInfo();
if (info.lastModified() >= modifiedAfter) {
totalSize += info.size();
++fileCount;
}
}
qDebug() << "Найдено файлов:" << fileCount << "общий размер:" << totalSize << "байт";
return totalSize;
}
// Поиск пустых директорий — пример обхода без фильтра по файлам
QStringList findEmptyDirectories(const QString &rootPath)
{
QStringList emptyDirs;
QDirIterator it(rootPath, QDir::Dirs | QDir::NoDotAndDotDot,
QDirIterator::Subdirectories);
while (it.hasNext()) {
QString dirPath = it.next();
QDir dir(dirPath);
if (dir.entryList(QDir::NoDotAndDotDot | QDir::AllEntries).isEmpty()) {
emptyDirs.append(dirPath);
}
}
return emptyDirs;
}
int main()
{
QElapsedTimer timer;
timer.start();
QStringList cppFiles = findFilesByExtension(".", {"*.cpp", "*.h", "*.hpp"});
qDebug() << "Найдено исходных файлов:" << cppFiles.size()
<< "за" << timer.elapsed() << "мс";
for (const QString &file : cppFiles.mid(0, 5))
qDebug() << " -" << file;
QDateTime weekAgo = QDateTime::currentDateTime().addDays(-7);
calculateTotalSize(".", weekAgo);
QStringList empties = findEmptyDirectories(".");
qDebug() << "Пустых директорий найдено:" << empties.size();
return 0;
}
Пояснения к коду
Конструктор QDirIterator(path, nameFilters, filters, flags) принимает маски имён файлов (поддерживает стандартные wildcard-шаблоны * и ?), фильтры типов записей и флаг рекурсии. Метод next() продвигает итератор и возвращает абсолютный путь к найденному элементу, а fileInfo() даёт доступ к полной информации (QFileInfo) без дополнительного системного вызова stat.
Флаг QDir::NoSymLinks исключает символические ссылки из обхода — это важно, чтобы избежать бесконечного цикла, если симлинк создаёт круговую ссылку на родительский каталог.
Подводные камни
- Циклы через символические ссылки. Без исключения симлинков (
QDir::NoSymLinks) рекурсивный обход может попасть в бесконечный цикл, если где-то в дереве есть ссылка, указывающая на родительскую или текущую директорию.QDirIteratorне имеет встроенной защиты от таких циклов.
- Производительность
entryInfoList()противQDirIterator. Для больших деревьев каталогов накопление полного списка черезentryInfoList()на каждом уровне рекурсии (если обход реализован вручную) расходует значительно больше памяти и времени, чем потоковый обход черезQDirIterator— особенно заметно на сетевых дисках с высокой латентностью.
- Права доступа к отдельным поддиректориям. Если у процесса нет прав на чтение какой-либо вложенной директории,
QDirIteratorпросто пропустит её содержимое без исключения — ошибку нужно дополнительно проверять черезQFileInfo::isReadable(), если это критично для логики приложения.
- Регистр символов в масках имён. На Linux маски имён файлов чувствительны к регистру (
*.CPPне совпадёт сfile.cpp), а на Windows — нет. Если код должен работать одинаково на всех платформах, стоит явно задаватьQDirIterator::NoIteratorFlagsсовместно с собственной фильтрацией черезQString::compare(Qt::CaseInsensitive).
- Блокировка UI при обходе больших деревьев. Обход файловой системы с десятками тысяч файлов в основном потоке приложения с интерфейсом вызывает заметные задержки. Для отзывчивого UI стоит выполнять обход в отдельном потоке (
QThread/QtConcurrent) и периодически сообщать о прогрессе через сигналы.
- Изменение файловой системы во время обхода. Если файлы добавляются или удаляются параллельно с обходом (например, другим процессом), результат может быть неполным или содержать ссылки на уже удалённые файлы — это нормальное поведение для большинства файловых систем, и код должен быть готов к ошибкам при последующей работе с найденными путями.
QDirIterator остаётся самым практичным инструментом Qt для обхода файловых деревьев — он сочетает простоту API с эффективностью по памяти, что особенно ценно при работе с большими файловыми хранилищами.
================================================================================