2012-08-13 54 views
5

是否有一種更有效的方式來從具有日期過濾器的目錄中填充文件名列表?帶日期過濾器的C#GetFiles

目前,我這樣做:

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

此經過的文件夾中的每個文件,我覺得應該有一個更 有效的方式。

+0

你可以嘗試使用LINQ。 – Bernard 2012-08-13 19:56:44

回答

5

我認爲你是在文件系統層面獲得更多的效率,而不是在C#的水平之後。如果是這樣的話,答案是:沒有辦法告訴文件系統按日期過濾。它將不必要地返回一切。

如果你是在CPU效率之後:這是沒有意義的,因爲將項目添加到列表框中比在日期上過濾要昂貴得多。優化你的代碼將不會產生任何結果。

16

首個解決方案:

你可以使用LINQ:

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

然後你可以使用的名稱直接在此列表中。

二解決方案:

另一種解決方案,使其更快可能是:

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

基準:

我通過使用此代碼做了一個標杆:

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

結果:

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

效率如何? – svick 2012-08-13 20:34:19

+3

LINQ比foreach效率低。它更清晰易讀,但在幕後生成相同的循環並添加自己的開銷。 – 2012-08-13 20:54:12

+0

好的,我編輯了我的代碼,我添加了另一個解決方案,並且我做了一個基準測試。 – 2012-08-13 21:59:42

4

我不想用正確的創建日期創建足夠的文件來做一個體面的基準,所以我做了一個更通用的版本,它需要一個開始和結束時間,並給出匹配的文件的名稱。讓它給出一個特定的昨天創建的文件的子字符串自然就是如此。

我想出了最快的單線程純.NET的回答是:

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

我本來期望EnumerateFiles()要稍微快一點,但事實證明,速度稍慢(可能會怎麼做,如果你最好通過網絡,但我沒有測試)。

有一個輕微的增益:

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

但不是很多,因爲它不利於實際調用GetFiles()。如果你沒有使用內核,或者GetFiles()沒有足夠大的結果,那麼它會讓事情變得更糟(AsParallel()的開銷大於並行過濾的好處)。另一方面,如果您也可以並行處理下一步的處理過程,那麼總體應用程序速度可能會提高。

EnumerateFiles()這樣做似乎沒有意義,因爲它似乎並不是很好並行,因爲它基於我將要使用的相同方法,而且本質上是串行的 - 需要以前的結果來生成下一個。

我得到的最快的是:

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

它冒險不具有FindClose()IDisposable答應了,並IEnumerator<string>手卷實施不僅應該作出這樣的容易做的(嚴重的理由這樣做),但也希望像3納秒或其他東西(不是一個嚴重的原因),但上面顯示的基本思路。

+0

你可以把'FindClose()'放在'finally'裏。在枚舉器的Dispose()被調用時('foreach'會自動執行),會執行出色的'finally'塊。 – svick 2012-08-14 18:31:29

+0

@svick你的確可以。在某些情況下,這種方法可能存在缺陷(通常如果枚舉器實際沒有枚舉),但是你是正確的 - 這不是其中之一。 – 2012-08-14 19:09:13