2012-08-13 10 views
5

Czy istnieje skuteczniejszy sposób wypełniania listy nazw plików z katalogu z filtrem daty?C# GetFiles z filtrem daty

Obecnie robię tak:

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

ten przechodzi przez każdy plik w folderze, a ja czuję się jak nie powinno być więcej efektywny sposób.

+0

Możesz spróbować użyć LINQ. – Bernard

Odpowiedz

5

myślę, że jesteś po uzyskaniu większej wydajności na poziomie systemu plików, a nie na poziomie C#. W takim przypadku odpowiedź brzmi: no: Nie ma możliwości poinformowania systemu plików, aby filtrował według daty. Będzie niepotrzebnie zwracać wszystko.

Jeśli zależy Ci na wydajności procesora: nie ma sensu, ponieważ dodawanie elementów do listbox jest tak niewiarygodnie droższe niż filtrowanie daty. Optymalizacja kodu nie przyniesie żadnych rezultatów.

16

Pierwsze rozwiązanie:

Można użyć LINQ:

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

Następnie można użyć bezpośrednio do tej listy nazwisk.

drugie rozwiązanie:

Innym rozwiązaniem, aby go szybciej mogą być:

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:

Zrobiłem odniesienia za pomocą tego kodu:

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

Wyniki:

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

Jak to jest bardziej wydajne? – svick

+3

LINQ jest mniej wydajny niż foreach. Jest czystszy i łatwiejszy do odczytania, ale generuje tę samą pętlę za kulisami i dodaje swój własny narzut. –

+0

Ok, redagowałem mój kod, dodałem inne rozwiązanie i zrobiłem test porównawczy. –

4

Nie miałem ochoty tworzyć wystarczającej liczby plików z odpowiednią datą utworzenia, aby zrobić przyzwoity test porównawczy, więc zrobiłem bardziej ogólną wersję, która zajmuje początek i koniec czasu i podaje nazwy pasujących do siebie plików. Sprawienie, by nadawało określony fragment plików utworzonych wczoraj, wynika oczywiście z tego.

Najszybszy jednowątkowy czysta NET odpowiedź wymyśliłem było:

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

Liczyłam EnumerateFiles() się nieco szybciej, ale okazało się nieco wolniej (może to zrobić lepiej, jeśli jesteś przechodzenie przez sieć, ale tego nie przetestowałem).

Jest niewielki zysk z:

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

Ale nie za wiele, ponieważ to nie pomaga rzeczywiste wezwanie do GetFiles(). Jeśli nie masz rdzeni do użycia, lub nie jest wystarczająco duży wynik z GetFiles(), to po prostu pogorszy to sytuację (koszty ogólne z AsParallel() są większe niż korzyści wynikające z jednoczesnego filtrowania).Z drugiej strony, jeśli możesz równolegle wykonywać kolejne kroki przetwarzania, ogólna szybkość aplikacji może się poprawić.

Wydaje się, że nie ma sensu robić tego z EnumerateFiles(), ponieważ nie wydaje się, aby dobrze paraliżować, ponieważ opiera się na tym samym podejściu, które zamierzam osiągnąć, a to jest z natury szeregowe - potrzebuję poprzedniego wyniku, aby wyprodukować Kolejny.

Najszybszy Dostałem:

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

To ryzykowny nie mający że FindClose() obiecane przez IDisposable i realizacja ręcznie zwijane z IEnumerator<string> powinny nie tylko sprawiają, że łatwiej zrobić (poważny powód, by to zrobić), ale miejmy nadzieję, że zgaśnie jak 3 nanosekundy lub coś w tym rodzaju (nie jest to poważny powód), ale powyższe pokazuje podstawowy pomysł.

+0

Możesz zająć się 'FindClose()' umieszczając go w 'finally'. Znakomite bloki 'finally' są wykonywane, gdy wywoływana jest funkcja' Dispose() '(która' foreach' działa automatycznie). – svick

+0

@svick Możesz rzeczywiście. W niektórych przypadkach mogą istnieć pułapki z tym podejściem (najczęściej jeśli wyliczacz nie jest faktycznie wyliczony), ale masz rację - to nie jest jedna z nich. –

Powiązane problemy