У меня есть каталог приблизительно с 15-30 тысячами файлов. Я должен просто вытянуть самый старый. Другими словами, тот, который был создан сначала. Существует ли быстрый способ сделать это использование C# кроме загрузки их в набор, тогда сортирующий?
Короткий ответ - нет. Файловые системы Windows не индексируют файлы по дате, поэтому не существует собственного способа сделать это, не говоря уже о .net способе без перечисления всех файлов.
Вам придется загрузить объекты FileInfo в коллекцию и отсортировать, но это делается в одну строку:
FileSystemInfo fileInfo = new DirectoryInfo(directoryPath).GetFileSystemInfos()
.OrderBy(fi => fi.CreationTime).First();
Хорошо, в две строки, потому что это длинное утверждение.
Редактировать: Удалена сортировка и преобразована в функцию.
public static FileInfo GetOldestFile(string directory)
{
if (!Directory.Exists(directory))
throw new ArgumentException();
DirectoryInfo parent = new DirectoryInfo(directory);
FileInfo[] children = parent.GetFiles();
if (children.Length == 0)
return null;
FileInfo oldest = children[0];
foreach (var child in children.Skip(1))
{
if (child.CreationTime < oldest.CreationTime)
oldest = child;
}
return oldest;
}
Вы не можете сделать это без сортировки, но что вы можете сделать, так это сделать ее быстрой.
Сортировка по CreationTime
может быть медленной, потому что первый доступ к этому свойству для каждого файла включает опрос файловой системы.
Используйте A Faster Directory Enumerator, который сохраняет больше информации о файлах при перечислении и позволяет быстрее выполнять сортировку.
Код для сравнения производительности:
static void Main(string[] args)
{
var timer = Stopwatch.StartNew();
var oldestFile = FastDirectoryEnumerator.EnumerateFiles(@"c:\windows\system32")
.OrderBy(f => f.CreationTime).First();
timer.Stop();
Console.WriteLine(oldestFile);
Console.WriteLine("FastDirectoryEnumerator - {0}ms", timer.ElapsedMilliseconds);
Console.WriteLine();
timer.Reset();
timer.Start();
var oldestFile2 = new DirectoryInfo(@"c:\windows\system32").GetFiles()
.OrderBy(f => f.CreationTime).First();
timer.Stop();
Console.WriteLine(oldestFile2);
Console.WriteLine("DirectoryInfo - {0}ms", timer.ElapsedMilliseconds);
Console.WriteLine("Press ENTER to finish");
Console.ReadLine();
}
Для меня это дает следующее:
VEN2232.OLB
FastDirectoryEnumerator - 27ms
VEN2232.OLB
DirectoryInfo - 559ms
Сортировка O (n log n)
. Вместо этого, почему бы вам просто не перечислить каталог? Я не уверен, что такое эквивалент C # FindFirstFile ()
/ FindNextFile ()
, но вы хотите сделать следующее:
Сохраните текущую наименьшую дату и имя файла в локальная переменная.
Пронумеровать каталог.
Вот подпрограмма C #, которая может делать то, что вы хотите, порождая командную оболочку, запускает dir / o: D
в указанном каталоге и возвращает имя первого найденного файла.
static string GetOldestFile(string dirName)
{
ProcessStartInfo si = new ProcessStartInfo("cmd.exe");
si.RedirectStandardInput = true;
si.RedirectStandardOutput = true;
si.UseShellExecute = false;
Process p = Process.Start(si);
p.StandardInput.WriteLine(@"dir " + dirName + " /o:D");
p.StandardInput.WriteLine(@"exit");
string output = p.StandardOutput.ReadToEnd();
string[] splitters = { Environment.NewLine };
string[] lines = output.Split(splitters, StringSplitOptions.RemoveEmptyEntries);
// find first line with a valid date that does not have a <DIR> in it
DateTime result;
int i = 0;
while (i < lines.Length)
{
string[] tokens = lines[i].Split(' ');
if (DateTime.TryParse(tokens[0], out result))
{
if (!lines[i].Contains("<DIR>"))
{
return tokens[tokens.Length - 1];
}
}
i++;
}
return "";
}
Если вы управляете каталогом (то есть, если ваши программы отвечают за создание и обслуживание всех файлов в этом каталоге), вам следует рассмотреть возможность отслеживания метаданных о каждом файле отдельно; возможно в базе данных.
Фактически, в этом может помочь тип столбца FileStream в SQL Server 2008. Вы можете создать таблицу, содержащую столбцы для имени файла, даты создания, даты изменения и столбец FileStream для содержимого. Вы можете найти такие вещи, как самый старый файл, используя индексы в столбцах метаданных. Вы можете найти контент, используя столбец FileStream.
Посмотрите, не было бы проще выполнить оболочку для скрытого процесса и перенаправить выходной поток на вход и использовать dir / od
, который сортирует по дате / времени, использование тире меняет порядок действий ....
Edit: вот пример кода, чтобы сделать это ... быстро и грязно ...
public class TestDir { private StringBuilder sbRedirectedOutput = new StringBuilder(); public string OutputData { get { return this.sbRedirectedOutput.ToString(); } } public void Run() { System.Diagnostics.ProcessStartInfo ps = new System.Diagnostics.ProcessStartInfo(); ps.FileName = "cmd"; ps.ErrorDialog = false; ps.Arguments = string.Format("dir {0} /o-d", path_name); ps.CreateNoWindow = true; ps.UseShellExecute = false; ps.RedirectStandardOutput = true; ps.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden; using (System.Diagnostics.Process proc = new System.Diagnostics.Process()) { proc.StartInfo = ps; proc.Exited += new EventHandler(proc_Exited); proc.OutputDataReceived += new System.Diagnostics.DataReceivedEventHandler(proc_OutputDataReceived); proc.Start(); proc.WaitForExit(); proc.BeginOutputReadLine(); while (!proc.HasExited) ; } } void proc_Exited(object sender, EventArgs e) { System.Diagnostics.Debug.WriteLine("proc_Exited: Process Ended"); } void proc_OutputDataReceived(object sender, System.Diagnostics.DataReceivedEventArgs e) { if (e.Data != null) this.sbRedirectedOutput.Append(e.Data + Environment.NewLine); //System.Diagnostics.Debug.WriteLine("proc_OutputDataReceived: Data: " + e.Data); } }
Самый первый 4 или 5 строк объекта StringBuilder sbRedirectedOutput
можно вырезать, тогда после этого строка будет содержать самое старое имя файла и ее будет довольно легко разобрать ....
Как ни странно, это отлично сработало в моем каталоге с более чем 3000 jpg-файлов:
DirectoryInfo di = new DirectoryInfo(dpath);
FileInfo[] rgFiles = di.GetFiles("*.jpg");
FileInfo firstfile = rgFiles[0];
FileInfo lastfile = rgFiles[rgFiles.Length - 1];
DateTime oldestfiletime = firstfile.CreationTime;
DateTime newestfiletime = lastfile.CreationTime;