开发者

how to get the oldest file in a directory fast using .NET?

开发者 https://www.devze.com 2022-12-22 17:31 出处:网络
I have a directory with around 15-30 thousand files. I need to just pull开发者_如何学运维 the oldest one. In other words the one that was created first. Is there a quick way to do this using C#, other

I have a directory with around 15-30 thousand files. I need to just pull开发者_如何学运维 the oldest one. In other words the one that was created first. Is there a quick way to do this using C#, other than loading them into a collection then sorting?


You will have to load the FileInfo objects into a collection & sort, but it's a one-liner:

FileSystemInfo fileInfo = new DirectoryInfo(directoryPath).GetFileSystemInfos()
    .OrderBy(fi => fi.CreationTime).First();

Ok, two lines because it's a long statement.


The short answer is no. Windows file systems don't index files by date so there is no native way to do this, let alone a .net way without enumerating all of them.


You can't do it without sorting but what you can do is make it fast.

Sorting by CreationTime can be slow because first accessing this property for each file involves interrogation of the file system.

Use A Faster Directory Enumerator that preserves more information about files while enumerating and allows to do sorting faster.

Code to compare performance:

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();
}

For me it gives this:

VEN2232.OLB

FastDirectoryEnumerator - 27ms

VEN2232.OLB

DirectoryInfo - 559ms


Edit: Removed the sort and made it a function.

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;
}


Sorting is O(n log n). Instead, why don't you just enumerate the directory? I'm not sure what the C# equivalent of FindFirstFile()/FindNextFile() is, but you want to do is:

  • Keep the current lowest date and filename in a local variable.

  • Enumerate the directory.

    • If the date on a given file is less than the local variable, set the local variable to the new date and filename.


Oddly enough, this worked perfectly on a directory of mine with 3000+ jpg files:

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;


Here's a C# routine that may do what you want by spawning a cmd shell execute a dir /o:D on the specified directory and returning the name of the first file found.

        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 "";
        }


Look, would it not be easier to shell out to a hidden process and redirect the output stream to the input and use the dir /o-d which sorts by the date/time, using the dash reverses the operation....

Edit: here's a sample code to do this...quick and dirty...

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);
        }
    }

The very first 4 or 5 lines of the StringBuilder object sbRedirectedOutput can be chopped out,then after that line would contain the oldest filename and would be quite easy to parse out....


I also have thousands of files. To retrieve them in sorted order by the date modified, use either of these C# statements.

files = di.GetFiles("*.*").OrderByDescending(f => f.LastWriteTime).ToArray();
files = di.GetFiles("*.*").OrderBy(f => f.LastWriteTime).ToArray();

To make it easier for the user to access the appropriate file, I display either of the following two lines. I have two windows open. One lists the files in descending order. The other list the files in ascending order. The descending order list is updated by Windows. The ascending order is not updated so the Hm key must be used to put the oldest file at the top of the list.

Console.WriteLine( "DateMod v (latest)");
Console.WriteLine( "DateMod ^ (oldest) Sel Hm");
0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号