У меня есть программа, которая пишет в базу данных, какие папки полны или пусты. Теперь я использую
bool hasFiles=false;
(Directory.GetFiles(path).Length >0) ? hasFiles=true: hasFiles=false;
но требуется почти один час, и я ничего не могу сделать в это время.
Там какой-либо самый быстрый путь состоит в том, чтобы проверить, имеет ли папка какой-либо файл?
Ключом к ускорению такого межсетевого поиска является сокращение количества запросов в сети. Вместо того, чтобы получать все каталоги и затем проверять каждый на наличие файлов, попробуйте получить все за один вызов.
В .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);
Написание этого для ленивости было бы интересным упражнением (потребление входных данных только тогда, когда что-то выполняет итерацию по одному из выходов, а другой буферизируется).
Я предполагаю (хотя точно не знаю), что поскольку вы вызываете GetFiles () на сетевом диске, это значительно увеличивает время получить все файлы из всех 30k папок и пересчитать их.
Я нашел альтернативный перечислитель каталогов здесь в CodeProject, который выглядит многообещающим.
В качестве альтернативы ... вы можете создать WebService на сервере, который перечисляет все за вас и после этого возвращает результаты.
РЕДАКТИРОВАТЬ : Я думаю, ваша проблема, скорее, связана с доступом к папке. Каждый раз, когда вы обращаетесь к каталогу на сетевом диске, вы будете проходить проверку безопасности и разрешений. Эти * 30k папок будут большим ударом по производительности. Я очень сомневаюсь, что использование FindFirstFile поможет, поскольку фактическое количество перечисленных файлов всегда будет равно 0 или 1.
Стоит упомянуть:
но это занимает почти час, и я ничего не могу сделать в это время . (курсив добавлен)
Вы делаете это из приложения с графическим интерфейсом пользователя в основном потоке? Если это так, отключите этот процесс, используя BackgroundWorker
. По крайней мере, тогда приложение будет продолжать реагировать. Вы также можете добавить в метод проверки для CancellationPending
и отменить его, если это займет слишком много времени.
В некоторой степени касательно вашего вопроса - просто кое-что, что я заметил и подумал, что прокомментирую.
Если вы используете .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 файл, папка не пуста
Лучше всего использовать функцию API FindFirstFile. Тогда это займет не так много времени.