Здравствуйте.
Решил потренироваться в работе с потоками в .NET 4.0 на следующей задачи:
Реализовать программу поиска дубликатов файлов по локальным дискам на компьютере.
Алгоритм работы программы:
• Подсчет папок на выбранных дисках (время: очень быстро);
• Сканирование папок на наличие файлов (время: нормальное);
• Сортировка файлов (время: очень быстро);
• Проверка заголовков файлов (время: нормальное);
• Полная проверка;
• Вывод дубликатов файлов в графический компонент (время: нормальное).
Алгоритм составлен по реализованной программе, скриншоты которой вот по этой
ссылке (размер = 461.17 Кб).
При реализации данной задачи возникло несколько вопросов:
1) Какой оптимальный по скорости алгоритм для поиска папок необходимо реализовать?
Я попробовал реализовать, вот что получилось (скорость работы конечно не устраивает):
private string Path { get; set; }
private string SearchPattern { get; set; }
private string[] NonSearhDirectorys { get; set; }
private const int DefaultCapacity = 10 * 100 * 1000;
public Search(string path, string searchPattern, string[] nonSearhDirectorys = null)
{
Path = path;
SearchPattern = searchPattern;
NonSearhDirectorys = nonSearhDirectorys;
}
private string[] GetFiles(string path)
{
return Directory.GetFiles(path, SearchPattern);
}
private static string[] GetFolders(string path)
{
return Directory.GetDirectories(path);
}
public IEnumerable<string> GetFolders()
{
IList<string> buf = new List<string>(DefaultCapacity);
Queue<string> dirs = new Queue<string>(DefaultCapacity);
dirs.Enqueue(Path);
string[] paths = null;
try
{
paths = GetFolders(dirs.Dequeue());
paths = paths.Except(NonSearhDirectorys).ToArray();
}
catch (UnauthorizedAccessException) { }
catch (DirectoryNotFoundException) { }
catch (PathTooLongException) { }
catch (ArgumentNullException) { }
if (paths != null && paths.Length > 0)
{
for (int i = 0; i < paths.Length; i++)
{
dirs.Enqueue(paths[i]);
buf.Add(paths[i]);
}
/*
Parallel.For(0, paths.Length, i =>
{
dirs.Enqueue(paths[i]);
buf.Add(paths[i]);
currentDir = paths[i];
});
*/
}
while (dirs.Count > 0)
{
string dir = dirs.Dequeue();
paths = null;
try
{
paths = GetFolders(dir);
}
catch (UnauthorizedAccessException) { }
catch (DirectoryNotFoundException) { }
catch (PathTooLongException) { }
catch (ArgumentNullException) { }
if (paths != null && paths.Length > 0)
{
for (int i = 0; i < paths.Length; i++)
{
dirs.Enqueue(paths[i]);
buf.Add(paths[i]);
}
/*
Parallel.For(0, paths.Length, i =>
{
dirs.Enqueue(paths[i]);
buf.Add(paths[i]);
});
*/
}
}
buf.Add(Path);
return buf.AsEnumerable();
}
2) Какой оптимальный алгоритм реализовать для поиска файлов в найденных папках?
Как реализовал:
public IEnumerable<File> GetFiles(IEnumerable<string> folders)
{
foreach (string folder in folders)
{
string[] paths = null;
try
{
paths = GetFiles(folder);
}
catch
{
}
if (paths != null && paths.Length > 0)
{
foreach (string path in paths)
{
yield return new File(path);
}
}
}
}
Где File
public class File :
IEqualityComparer<File>,
IComparable,
IComparable<File>
{
public string Path { get; private set; }
public string Name { get; private set; }
public DateTime LastWriteTime { get; set; }
public long Size { get; private set; }
private static FileInfo _fileInfo;
public File(string path, string name, DateTime lastWriteTime, long size)
{
Path = path;
Name = name;
LastWriteTime = lastWriteTime;
Size = size;
}
public File(string path) : this(path,
File.GetName(path), File.GetLastWriteTime(path), File.GetSize(path))
{
}
public int CompareTo(object obj)
{
return Size.CompareTo(obj as File);
}
public int CompareTo(File other)
{
return Size.CompareTo(other.Size);
}
public override bool Equals(object obj)
{
return obj is File && Equals((File) obj);
}
public bool Equals(File other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return Equals(other.Name, Name) &&
other.LastWriteTime.Equals(LastWriteTime) &&
other.Size == Size;
}
public bool Equals(File x, File y)
{
return x.Equals(y);
}
public override int GetHashCode()
{
unchecked
{
int result = (Name != null ? Name.GetHashCode() : 0);
result = (result*397) ^ LastWriteTime.GetHashCode();
result = (result*397) ^ Size.GetHashCode();
return result;
}
}
public int GetHashCode(File obj)
{
return obj.GetHashCode();
}
public static string GetName(string path)
{
if (String.IsNullOrEmpty(path))
{
throw new ArgumentNullException("path");
}
if (path.Length >= 260)
{
return default(string);
}
_fileInfo = new FileInfo(path);
return _fileInfo.Name;
}
public static long GetSize(string path)
{
if (String.IsNullOrEmpty(path))
{
throw new ArgumentNullException("path");
}
if (path.Length >= 260)
{
return default(long);
}
_fileInfo = new FileInfo(path);
return _fileInfo.Length;
}
public static int GetSizeInMegaBytes(string path)
{
if (String.IsNullOrEmpty(path))
{
throw new ArgumentNullException("path");
}
if (path.Length >= 260)
{
return default(int);
}
_fileInfo = new FileInfo(path);
long buf = _fileInfo.Length / 1024;
return (int)buf / 1024;
}
public static DateTime GetLastWriteTime(string path)
{
if (String.IsNullOrEmpty(path))
{
throw new ArgumentNullException("path");
}
if (path.Length >= 260)
{
return default(DateTime);
}
_fileInfo = new FileInfo(path);
return _fileInfo.LastWriteTime;
}
3) При выполнении в созданном потоке (или в экземпляре Task),
если мы хотим отобразить в графический компонент какой-либо результат,
правильно ли пользоваться вот такой схемой?
if (ГрафическийКомпонент1.InvokeRequired ||
ГрафическийКомпонент2.InvokeRequired || … ||
ГрафическийКомпонентN.InvokeRequired)
{
Action actionГрафическийКомпонент1 = () => … ;
Action actionГрафическийКомпонент2 = () => … ;
…
Action actionГрафическийКомпонентN = () => … ;
ГрафическийКомпонент1.Invoke(actionГрафическийКомпонент1);
ГрафическийКомпонент2.Invoke(actionГрафическийКомпонент2);
…
ГрафическийКомпонентN.Invoke(actionГрафическийКомпонентN);
else
{
вызов методом не через делегат Action
}
4) Из алгоритма работы программы (1 пункт).
Как нам при работе в потоке метода подсчета папок возвращать их количество
в компонент label не привязывая метод подсчета папок к GUI (то есть разделенная логика)?
5) Такой же вопрос, только если нам необходимо возвращать множество данных
в графический компонент при работе одного метода в потоке и, не привязываясь к GUI?
6) Из алгоритма работы программы (4 пункт).
Как проверять заголовок файла и что это вообще такое?
7) Из алгоритма работы программы (последний пункт).
Как быстро вывести множество элементов (дублированных файлов) в графический компонент?
Может быть, использовать виртуальный DataGridView?
8) Возможно ли применение, каких – либо шаблонов проектирования в данной задачи? Если не сложно, скажите каких.
Заранее огромное спасибо!
P.S. По возможности буду сразу отвечать на новые сообщения в той теме и обязательно оценивать =)
Ссылка на текстовую версию темы (документ Word 97-2003)
Здравствуйте, xobotik, Вы писали:
На будущее — длинные простыни кода и пространные рассуждения очень маловероятно что кто то читать будет.
Теперь по делу:
1) Parallel в данном случае, имхо, слишком низкоуровневое и, как следствие, не очень удобное средство. Нужно использовать PLinq и фьючерсы (Task<T>).
2) Скачай и поковыряй VS Async CTP, все равно ведь баловством занимаешься.
3) Еще можно побаловаться с RX, но в фремворке точно нет асинхронного чтения списка файлов в директории, а есть ли оно на уровне WinAPI я ХЗ.
... << RSDN@Home 1.2.0 alpha 4 rev. 1476 on Windows 7 6.1.7600.0>>
Здравствуйте, xobotik, Вы писали:
AVK>>На будущее — длинные простыни кода и пространные рассуждения очень маловероятно что кто то читать будет.
X>Хммм =) я как бы еще сокращал и час целый работал над сообщением=) хотел просто все по делу и кратко =))
Да я все понимаю. Но обычно люди ценят свое время. Поэтому лучше дать краткий дайджест, а детали раскрыть ниже или вообще по требованию.
X>На ус намотаю, только вот что там с остановками работы Task , продолжением и так далее, не разобрался.
Ну вот заодно и разберешься. И не надо будет кучу очередей руками городить.
... << RSDN@Home 1.2.0 alpha 4 rev. 1476 on Windows 7 6.1.7600.0>>