2012-08-13 72 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