2010-03-08 22 views
10

Tengo un directorio con alrededor de 15-30 mil archivos. Necesito simplemente sacar el más viejo. En otras palabras, el que fue creado primero. ¿Hay una manera rápida de hacer esto usando C#, aparte de cargarlos en una colección y luego ordenarlos?cómo obtener el archivo más antiguo en un directorio rápido con .NET?

+5

Yo tenía entendido que tener un gran número de archivos en un directorio reduce el rendimiento. Piensa que has pasado ese punto. –

+0

Uhm, lo que dijo John. Agregue algunas barras para dividirlo en subdirectorios. –

+0

A menos que necesite hacer eso varias veces por segundo, 15-30k no son tantos archivos. – Joey

Respuesta

7

La respuesta corta es no. Los sistemas de archivos de Windows no indexan los archivos por fecha, por lo que no existe una forma nativa de hacerlo, y mucho menos un camino .net sin enumerarlos todos.

+0

¿Qué pasa con el objeto FileSystemInfo? – andrecarlucci

2

La clasificación es O(n log n). En cambio, ¿por qué no solo enumera el directorio? No estoy seguro de lo que el equivalente C# de FindFirstFile()/FindNextFile() es, pero quiero hacer es:

  • mantener actualizada la fecha y el nombre más bajo en una variable local.

  • Enumerar el directorio.

    • Si la fecha de un archivo determinado es menor que la variable local, establezca la variable local en la nueva fecha y nombre de archivo.
+0

Realmente no hay FindFirst/Next. Está envuelto en Directory.GetFiles. Pero tienes razón en que lo único que necesitas es una búsqueda lineal, que es directa O (n). –

+2

En ese caso, podría incluso preguntarme si podría reducir algunos ciclos pinvoking las API de enumeración nativas ... No hay necesidad de hacer una asignación de memoria adicional aquí para obtener una gran variedad. Aún así, no obtendrás mejores resultados que 'O (n)' (en ese sentido, la sugerencia DB es mejor), pero puedes obtener 'O (n)' con un factor constante menor y eso podría ser suficiente. – asveikau

+0

Además, las API de enumeración nativa le dan la hora y el nombre del archivo al mismo tiempo; no parece que 'GetFiles()' lo haga. – asveikau

12

Si controlas el directorio (es decir, si sus programas son responsables de la creación y el mantenimiento de todos los archivos de ese directorio), entonces debería considerar el seguimiento de los metadatos acerca de cada archivo por separado; quizás en una base de datos.

De hecho, el tipo de columna FileStream en SQL Server 2008 puede ayudar con esto. Puede crear una tabla que contenga columnas para nombre de archivo, crear fecha, modificar fecha y una columna FileStream para el contenido. Puede encontrar cosas como el archivo más antiguo usando índices en las columnas de metadatos. Puede encontrar el contenido utilizando la columna FileStream.

+0

Esto no está mal, pero bien puede ser excesivo. –

+0

@Steven: Teniendo en cuenta la cantidad de archivos, tal vez no.También me pregunto qué otras operaciones de base de datos se realizan en tales directorios. Todo es discutible si él no controla los directorios involucrados. –

+0

Me pregunto cuál es el panorama general aquí. –

0

Mira, ¿no sería más fácil de pagar a un proceso oculto y redirigir el flujo de salida a la entrada y el uso de la dir /o-d que clasifica por la fecha/hora, usando el tablero invierte la operación ....

Editar: aquí hay un código de ejemplo para hacer esto ... rápido y sucio ...

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

los primeros 4 o 5 líneas del objeto StringBuilder sbRedirectedOutput se pueden cortar a cabo, a continuación, después de que la línea contendría el nombre de archivo más antiguo y sería bastante fácil de analizar ...

+0

No veo el beneficio de esto. –

+0

@Steven: la lista del directorio podría redirigirse a una cadena, y es cuestión de elegir el nombre del archivo en la parte superior de la lista ... – t0mm13b

+2

"Mira, ¿no sería más fácil ..." Ummm ... no . –

4

Editar: Eliminó el género y lo convirtió en una función.

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

Aquí está una rutina de C# que puede hacer lo que quiera por el desove de una consola CMD ejecutar un dir /o:D en el directorio especificado y devolver el nombre del primer archivo encontrado.

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

Estaba pensando en seguir esta ruta, pero es casi lo mismo que un enfoque LINQ ¿verdad? –

+0

Aunque esta solución es correcta, es lenta y está llena de casos de esquina. El comprador tenga cuidado. – rpetrich

15

Usted tendrá que cargar los objetos de FileInfo en una especie recogida &, pero es una sola línea:

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

Ok, dos líneas porque es una larga declaración.

+0

Gran respuesta simple. No será mejor que esto sin un cambio en la estructura del directorio IMO. –

+1

Nunca ordene una colección si su objetivo es solo devolver un solo artículo. Use Agregado() en su lugar. Para ver un ejemplo, consulte: http://stackoverflow.com/questions/10120944/c-sharp-finding-nearest-number-in-array/13896732#13896732 –

+0

@LeeGrissom gracias por el enlace; Realmente no había considerado ninguna sobrecarga antes, ya que asumí que el 'OrderBy' era flojo y no se evaluaría del todo, pero no se había probado ni comprobado. –

4

No puede hacerlo sin ordenar, pero lo que puede hacer es hacerlo rápido.

La ordenación por CreationTime puede ser lenta porque el primer acceso a esta propiedad para cada archivo implica la interrogación del sistema de archivos.

Use A Faster Directory Enumerator que conserva más información sobre los archivos al enumerar y permite hacer la ordenación más rápido.

Código de comparar el rendimiento:

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

Para mí esto da:

VEN2232.OLB

FastDirectoryEnumerator - 27 ms

VEN2232.OLB

DirectoryInfo - 559m s

+0

Todo lo que puedo decir es ¡WoW - ShamWoW! Muchas gracias por esto, ciertamente implementaré esto. –

+1

@ Konstantin: en .NET 4.0, el método 'DirectoryInfo.GetFiles()' ahora conserva la información adicional, por lo que no sufre el problema de rendimiento al que se ha dirigido. –

0

Por extraño que parezca, esto funcionó perfectamente en un directorio de la mía con 3000 + archivos 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; 
Cuestiones relacionadas