Проверка наличия файлов в папке - PullRequest
5 голосов
/ 26 апреля 2010

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

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

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

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

Ответы [ 6 ]

10 голосов
/ 21 марта 2013

Чтобы проверить, существуют ли какие-либо файлы в каталоге или подкаталогах, в .net 4 вы можете использовать метод ниже:

public bool isDirectoryContainFiles(string path) {
    if (!Directory.Exists(path)) return false;
    return Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories).Any();
}
5 голосов
/ 26 апреля 2010

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

В .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);

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

4 голосов
/ 26 апреля 2010

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

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

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

3 голосов
/ 26 апреля 2010

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

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

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

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

2 голосов
/ 26 апреля 2010

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

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

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

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

0 голосов
/ 26 апреля 2010

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...