2012-08-13 38 views
5

¿Existe alguna manera más eficaz de completar una lista de nombres de archivo desde un directorio con un filtro de fecha?C# GetFiles con el filtro de fecha

Actualmente, estoy haciendo esto:

foreach (FileInfo flInfo in directory.GetFiles()) 
{ 
    DateTime yesterday = DateTime.Today.AddDays(-1); 
    String name = flInfo.Name.Substring(3,4); 
    DateTime creationTime = flInfo.CreationTime; 
    if (creationTime.Date == yesterday.Date) 
     yesterdaysList.Add(name); 
} 

Esto ocurre a través de cada archivo en la carpeta, y siento que debería haber una manera más eficiente.

+0

Puede intentar usar LINQ. – Bernard

Respuesta

5

creo que usted está después de conseguir una mayor eficiencia en el nivel de sistema de archivos, no en el nivel C#. Si ese es el caso, la respuesta es no: no hay forma de decirle al sistema de archivos que filtre por fecha. Va a devolver todo innecesariamente.

Si busca la eficiencia de la CPU: Esto no tiene sentido porque agregar elementos a un cuadro de lista es tan increíblemente más caro que filtrar en la fecha. Optimizar tu código no arrojará resultados.

16

Primera Solución:

Usted puede utilizar LINQ:

List<string> yesterdaysList = directory.GetFiles().Where(x => x.CreationTime.Date == DateTime.Today.AddDays(-1)) 
                .Select(x => x.Name) 
                .ToList(); 

continuación, puede utilizar directamente esta lista de nombres.

Segunda solución:

Otra solución para hacerlo más rápido podría ser:

DateTime yesterday = DateTime.Today.AddDays(-1); //initialize this variable only one time 

foreach (FileInfo flInfo in directory.GetFiles()){ 
    if (flInfo.CreationTime.Date == yesterday.Date) //use directly flInfo.CreationTime and flInfo.Name without create another variable 
     yesterdaysList.Add(flInfo.Name.Substring(3,4)); 
} 

Benchmark:

hice un punto de referencia mediante el uso de este código:

class Program { 
    static void Main(string[ ] args) { 
     DirectoryInfo directory = new DirectoryInfo(@"D:\Films"); 
     Stopwatch timer = new Stopwatch(); 
     timer.Start(); 

     for (int i = 0; i < 100000; i++) { 
      List<string> yesterdaysList = directory.GetFiles().Where(x => x.CreationTime.Date == DateTime.Today.AddDays(-1)) 
               .Select(x => x.Name) 
               .ToList(); 
     } 

     timer.Stop(); 
     TimeSpan elapsedtime = timer.Elapsed; 
     Console.WriteLine(string.Format("{0:00}:{1:00}:{2:00}", elapsedtime.Minutes, elapsedtime.Seconds, elapsedtime.Milliseconds/10)); 
     timer.Restart(); 

     DateTime yesterday = DateTime.Today.AddDays(-1); //initialize this variable only one time 
     for (int i = 0; i < 100000; i++) { 
      List<string> yesterdaysList = new List<string>(); 

      foreach (FileInfo flInfo in directory.GetFiles()) { 
       if (flInfo.CreationTime.Date == yesterday.Date) //use directly flInfo.CreationTime and flInfo.Name without create another variable 
        yesterdaysList.Add(flInfo.Name.Substring(3, 4)); 
      } 
     } 


     timer.Stop(); 
     elapsedtime = timer.Elapsed; 
     Console.WriteLine(string.Format("{0:00}:{1:00}:{2:00}", elapsedtime.Minutes, elapsedtime.Seconds, elapsedtime.Milliseconds/10)); 
     timer.Restart(); 

     for (int i = 0; i < 100000; i++) { 
      List<string> list = new List<string>(); 

      foreach (FileInfo flInfo in directory.GetFiles()) { 
       DateTime _yesterday = DateTime.Today.AddDays(-1); 
       String name = flInfo.Name.Substring(3, 4); 
       DateTime creationTime = flInfo.CreationTime; 
       if (creationTime.Date == _yesterday.Date) 
        list.Add(name); 
      } 
     } 

     elapsedtime = timer.Elapsed; 
     Console.WriteLine(string.Format("{0:00}:{1:00}:{2:00}", elapsedtime.Minutes, elapsedtime.Seconds, elapsedtime.Milliseconds/10)); 
    } 
} 

Resultados:

First solution: 00:19:84 
Second solution: 00:17:64 
Third solution: 00:19:91 //Your solution 
+0

¿Cómo es eso más eficiente? – svick

+3

LINQ es menos eficiente que un foreach. Es más limpio y fácil de leer, pero está generando el mismo bucle detrás de escena y agregando su propia sobrecarga. –

+0

Ok, edité mi código, agregué otra solución e hice un punto de referencia. –

4

No tuve ganas de crear suficientes archivos con la fecha de creación correcta para hacer un punto de referencia decente, así que hice una versión más general que toma una hora de inicio y finalización y da los nombres de los archivos que coinciden. Hacerlo dar una subcadena particular de archivos creados ayer sigue naturalmente de eso.

La respuesta más rápida .NET pura de un solo subproceso que se me ocurrió fue:

private static IEnumerable<string> FilesWithinDates(string directory, DateTime minCreated, DateTime maxCreated) 
{ 
    foreach(FileInfo fi in new DirectoryInfo(directory).GetFiles()) 
     if(fi.CreationTime >= minCreated && fi.CreationTime <= maxCreated) 
      yield return fi.Name; 
} 

lo que habría esperado EnumerateFiles() a ser un poco más rápido, pero resultó un poco más lento (que podría hacerlo mejor si estás revisando una red, pero no lo probé).

Hay una ligera ganancia con:

private static ParallelQuery<string> FilesWithinDates(string directory, DateTime minCreated, DateTime maxCreated) 
{ 
    return new DirectoryInfo(directory).GetFiles().AsParallel() 
     .Where(fi => fi.CreationTime >= minCreated && fi.CreationTime <= maxCreated) 
     .Select(fi => fi.Name); 
} 

pero no mucho, ya que no ayuda a la llamada real a GetFiles(). Si no tiene los núcleos para usar, o no hay un resultado lo suficientemente grande como GetFiles(), empeorará las cosas (los gastos generales de AsParallel() superan el beneficio de realizar el filtrado en paralelo).Por otro lado, si puede hacer sus próximos pasos de procesamiento también en paralelo, entonces la velocidad general de la aplicación podría mejorar.

Parece que no tiene sentido hacer esto con EnumerateFiles() porque no parece paralelizarse bien, porque está basado en el mismo enfoque al que voy a llegar, y que es intrínsecamente serial - necesita el resultado previo para producir el siguiente.

El más rápido lo que obtuve fue:

public const int MAX_PATH = 260; 
public const int MAX_ALTERNATE = 14; 

[StructLayoutAttribute(LayoutKind.Sequential)] 
public struct FILETIME 
{ 
    public uint dwLowDateTime; 
    public uint dwHighDateTime; 
    public static implicit operator long(FILETIME ft) 
    { 
     return (((long)ft.dwHighDateTime) << 32) | ft.dwLowDateTime; 
    } 
}; 

[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)] 
public struct WIN32_FIND_DATA 
{ 
    public FileAttributes dwFileAttributes; 
    public FILETIME ftCreationTime; 
    public FILETIME ftLastAccessTime; 
    public FILETIME ftLastWriteTime; 
    public uint nFileSizeHigh; 
    public uint nFileSizeLow; 
    public uint dwReserved0; 
    public uint dwReserved1; 
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst=MAX_PATH)] 
    public string cFileName; 
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst=MAX_ALTERNATE)] 
    public string cAlternate; 
} 

[DllImport("kernel32", CharSet=CharSet.Unicode)] 
public static extern IntPtr FindFirstFile(string lpFileName, out WIN32_FIND_DATA lpFindFileData); 

[DllImport("kernel32", CharSet=CharSet.Unicode)] 
public static extern bool FindNextFile(IntPtr hFindFile, out WIN32_FIND_DATA lpFindFileData); 

[DllImport("kernel32.dll")] 
public static extern bool FindClose(IntPtr hFindFile); 

private static IEnumerable<string> FilesWithinDates(string directory, DateTime minCreated, DateTime maxCreated) 
{ 
    long startFrom = minCreated.ToFileTimeUtc(); 
    long endAt = maxCreated.ToFileTimeUtc(); 
    WIN32_FIND_DATA findData; 
    IntPtr findHandle = FindFirstFile(@"\\?\" + directory + @"\*", out findData); 
    if(findHandle != new IntPtr(-1)) 
    { 
     do 
     { 
      if(
       (findData.dwFileAttributes & FileAttributes.Directory) == 0 
       && 
       findData.ftCreationTime >= startFrom 
       && 
       findData.ftCreationTime <= endAt 
      ) 
      { 
       yield return findData.cFileName; 
      } 
     } 
     while(FindNextFile(findHandle, out findData)); 
     FindClose(findHandle); 
    } 
} 

Es arriesgado no tener que FindClose() prometido por un IDisposable, y una aplicación enrollado a mano de IEnumerator<string> no sólo debe hacer que sea más fácil hacer (razón de peso para hacerlo) pero también se espera que se reduzca como 3 nanosegundos o algo así (no es una razón seria para hacerlo), pero lo anterior muestra la idea básica.

+0

Puede ocuparse de 'FindClose()' colocándolo en 'finally'. Los bloques "finalmente" sobresalientes se ejecutan cuando se llama a 'Dispose()' del enumerador (que 'foreach' hace automáticamente). – svick

+0

@svick De hecho, puedes. En algunos casos puede haber trampas con ese enfoque (la mayoría de las veces si el enumerador no está realmente enumerado), pero está en lo cierto, este no es uno de ellos. –

Cuestiones relacionadas