Проверка, если папка имеет файлы

У меня есть программа, которая пишет в базу данных, какие папки полны или пусты. Теперь я использую

bool hasFiles=false;
(Directory.GetFiles(path).Length >0) ? hasFiles=true: hasFiles=false;

но требуется почти один час, и я ничего не могу сделать в это время.

Там какой-либо самый быстрый путь состоит в том, чтобы проверить, имеет ли папка какой-либо файл?

5
задан ChrisF 26 April 2010 в 11:29
поделиться

5 ответов

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

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

Используя DirectoryInfo , можно также получить информацию о том, является ли возвращаемое имя файлом или каталогом, что также сокращает количество вызовов.

Это означает, что разделение списка всех каталогов и файлов выглядит примерно так:

struct AllDirectories {
  public List<string> DirectoriesWithoutFiles { get; set; }
  public List<string> DirectoriesWithFiles { get; set; }
}

static class FileSystemScanner {
  public AllDirectories DivideDirectories(string startingPath) {
    var startingDir = new DirectoryInfo(startingPath);

    // allContent IList<FileSystemInfo>
    var allContent = GetAllFileSystemObjects(startingDir);
    var allFiles = allContent.Where(f => !(f.Attributes & FileAttributes.Directory))
                             .Cast<FileInfo>();
    var dirs = allContent.Where(f => (f.Attributes & FileAttributes.Directory))
                         .Cast<DirectoryInfo>();
    var allDirs = new SortedList<DirectoryInfo>(dirs, new FileSystemInfoComparer());

    var res = new AllDirectories {
      DirectoriesWithFiles = new List<string>()
    };
    foreach (var file in allFiles) {
      var dirName = Path.GetDirectoryName(file.Name);
      if (allDirs.Remove(dirName)) {
        // Was removed, so first time this dir name seen.
        res.DirectoriesWithFiles.Add(dirName);
      }
    }
    // allDirs now just contains directories without files
    res.DirectoriesWithoutFiles = new List<String>(addDirs.Select(d => d.Name));
  }

  class FileSystemInfoComparer : IComparer<FileSystemInfo> {
    public int Compare(FileSystemInfo l, FileSystemInfo r) {
      return String.Compare(l.Name, r.Name, StringComparison.OrdinalIgnoreCase);
    }
  }
}

Реализация GetAllFileSystemObjects зависит от версии .NET. На .NET 4 это очень просто:

ILIst<FileSystemInfo> GetAllFileSystemObjects(DirectoryInfo root) {
  return root.GetFileSystemInfos("*.*", SearchOptions.AllDirectories);
}

В более ранних версиях требуется немного больше работы:

ILIst<FileSystemInfo> GetAllFileSystemObjects(DirectoryInfo root) {
  var res = new List<FileSystemInfo>();
  var pending = new Queue<DirectoryInfo>(new [] { root });

  while (pending.Count > 0) {
    var dir = pending.Dequeue();
    var content = dir.GetFileSystemInfos();
    res.AddRange(content);
    foreach (var dir in content.Where(f => (f.Attributes & FileAttributes.Directory))
                               .Cast<DirectoryInfo>()) {
      pending.Enqueue(dir);
    }
  }

  return res;
}

Этот подход вызывает файловую систему как можно меньше раз, только один раз в .NET 4 или один раз для каждого каталога в более ранних версиях, что позволяет сетевой клиент и сервер, чтобы свести к минимуму количество обращений к базовой файловой системе и круговых обходов сети.

Получение экземпляров FileSystemInfo имеет недостаток, заключающийся в необходимости выполнения нескольких операций с файловой системой (я считаю, что это в некоторой степени зависит от ОС), но для каждого имени любое решение должно знать, является ли это файлом или каталогом, так что это на каком-то уровне избежать невозможно (без использования P / Invoke FindFileFirst / FindNextFile / FindClose ).


Кроме того, описанное выше было бы проще с методом расширения раздела:

Tuple<IEnumerable<T>,IEnumerable<T>> Extensions.Partition<T>(
                                                 this IEnumerable<T> input,
                                                 Func<T,bool> parition);

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

5
ответ дан 18 December 2019 в 06:34
поделиться

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

Я нашел альтернативный перечислитель каталогов здесь в CodeProject, который выглядит многообещающим.

В качестве альтернативы ... вы можете создать WebService на сервере, который перечисляет все за вас и после этого возвращает результаты.

РЕДАКТИРОВАТЬ : Я думаю, ваша проблема, скорее, связана с доступом к папке. Каждый раз, когда вы обращаетесь к каталогу на сетевом диске, вы будете проходить проверку безопасности и разрешений. Эти * 30k папок будут большим ударом по производительности. Я очень сомневаюсь, что использование FindFirstFile поможет, поскольку фактическое количество перечисленных файлов всегда будет равно 0 или 1.

3
ответ дан 18 December 2019 в 06:34
поделиться

Стоит упомянуть:

но это занимает почти час, и я ничего не могу сделать в это время . (курсив добавлен)

Вы делаете это из приложения с графическим интерфейсом пользователя в основном потоке? Если это так, отключите этот процесс, используя BackgroundWorker . По крайней мере, тогда приложение будет продолжать реагировать. Вы также можете добавить в метод проверки для CancellationPending и отменить его, если это займет слишком много времени.

В некоторой степени касательно вашего вопроса - просто кое-что, что я заметил и подумал, что прокомментирую.

2
ответ дан 18 December 2019 в 06:34
поделиться

Если вы используете .Net 4.0, обратите внимание на метод EnumerateFiles. http://msdn.microsoft.com /en-us/library/dd413232(v=VS.100).aspx

Методы EnumerateFiles и GetFiles различаются следующим образом: Когда вы используете EnumerateFiles, вы можете запустить {{1 }} перечисление коллекции объектов FileInfo перед возвратом всей коллекции ; когда вы используете GetFiles, вы должны дождаться возврата всего массива объектов FileInfo , прежде чем вы сможете получить доступ к массиву. Следовательно, когда вы находитесь работая с множеством файлов и каталогов, EnumerateFiles может быть более эффективным.

Таким образом, не все файлы извлекаются из папки, если в счетчике есть хотя бы 1 файл, папка не пуста

4
ответ дан 18 December 2019 в 06:34
поделиться

Лучше всего использовать функцию API FindFirstFile. Тогда это займет не так много времени.

0
ответ дан 18 December 2019 в 06:34
поделиться
Другие вопросы по тегам:

Похожие вопросы: