EnumerateFiles()如何工作?

时间:2013-04-16 21:01:26

标签: c# file-io

我正在编写一个程序,当给定日期,文件夹路径和文件扩展名时,将查看该文件夹,并将查找从月初到当前日期的上次访问时间的所有文件,并且只包含传递文件扩展名的文件。

我正在寻找的文件总是在文件夹树中的同一级别,所以我可以在程序中编写多长时间来查找文件。

目前我的节目大约需要一分钟,所以今天(第16次)需要大约16分半钟。

我想创建一个程序来填充查找文件夹路径中日期范围的所有文件,并从文件中提取信息。我只是不想编码程序在我的业务改变他们存储文件的方式时要看多深。

我设法制作代码,如果给定一个文件夹,程序将显示日期范围的所有文件的名称,但需要25分钟。这是代码

TimeSpan BeginningTime = DateTime.Now.TimeOfDay;
DateTime BeginningDate = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1);
DateTime EndingDate = DateTime.Now;
string[] FoldersToLookAt = { @"e:\", @"e:\Kodak Images\", @"e:\images\", @"e:\AFSImageMerge\" };
foreach (string FolderPath in FoldersToLookAt)
{
    for (DateTime Date = BeginningDate; Date <= EndingDate; Date = Date.AddDays(1))
    {
        string DateString = Date.ToString("yyMMdd");
        string FilePath = (FolderPath + DateString);
        DirectoryInfo FilesToLookThrough = new DirectoryInfo(FilePath);
        if (FilesToLookThrough.Exists)
        {
            foreach (var MyFile in FilesToLookThrough.EnumerateFiles("*.dat", SearchOption.AllDirectories))
            {
                if (MyFile.LastAccessTime >= BeginningDate)
                {
                    Console.WriteLine(MyFile.FullName);
                }
            }
        }
    }
}

从我看到它首先获取所有文件,然后浏览所有文件并打印出来 所有上次访问时间大于开始日期的文件。

他们在C#中的方式是从文件中提取信息而不是将其存储在列表中吗?或者我是否必须从头开始构建程序?

3 个答案:

答案 0 :(得分:2)

你的问题不是很清楚,但是看看你的代码,以及你想要实现的目标,我建议你摆脱引导你完成日期文件夹的循环。只需在每个顶级文件夹下使用“AllDirectories”选项即可。它是递归的,所以它将通过尽可能多的级别。

TimeSpan BeginningTime = DateTime.Now.TimeOfDay;
DateTime BeginningDate = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1);
DateTime EndingDate = DateTime.Now;
string[] FoldersToLookAt = { @"e:\", @"e:\Kodak Images\", @"e:\images\", @"e:\AFSImageMerge\" };
foreach (string FolderPath in FoldersToLookAt)
{
    FilesToLookThrough = new DirectoryInfo(FolderPath);
    if (FilesToLookThrough.Exists)
    {
        foreach (var MyFile in FilesToLookThrough.EnumerateFiles("*.dat", SearchOption.AllDirectories))
        {
            if (MyFile.LastAccessTime >= BeginningDate)
            {
                Console.WriteLine(MyFile.FullName);
            }
        }
    }
}
编辑:另一个答案很有用,因为你要经历“e:\”,你可能不需要通过其他“FoldersToLookAt”,因为无论如何它们都将被搜索。您最终可能会得到的是同一文件的多个列表。如果你把它们拿走,它会运行得更快。

你看,你的代码首先非常接近。使用这种方法可以剪切出一个完整的循环,“AllDirectories”搜索选项将确保您以递归方式查看所有子文件夹。您也可以防止您的组织决定不将内容存储在按日期等命名的文件夹中,现在程序的运行时间仅与文件数成比例。

现在,为了获得额外的功劳,可以通过不为每个项目使用Console.WriteLine来提高性能。更快的方法是使用StringBuilder,然后在结尾处吐出结果。

// At the top of your program
StringBuilder sb = new StringBuilder();
// BeginningTime, BeginningDate, etc...

// Before the first loop
Console.WriteLine("Working...");

// Inside the very inner if, where your Console.WriteLine was
sb.AppendLine(MyFile.FullName);


// After the end of the outer loop
Console.WriteLine(sb.ToString());

为什么这会让它变得更好?写入控制台的速度非常慢,它实际上涉及将Windows发送到内核模式并返回,它确实很慢。做一次,具有更大块文本的事件比做很多事情要快得多。现在,为什么要使用StringBuilder而不仅仅是做一个好旧的:

string output;

for(...)
{
     output += filename + Environment.NewLine;
}

在C#中,为彼此添加两个字符串会创建一个新字符串。一遍又一遍地执行此操作也很慢,尤其是当新字符串变大时。 StringBuilder只维护所有字符串的列表,并在调用ToString()时创建一个新缓冲区并将其全部复制。

答案 1 :(得分:0)

考虑到您的要求,您的程序看起来要复杂得多。

string[] FoldersToLookAt = { @"e:\",
                             @"e:\Kodak Images\",    // do you really need these,
                             @"e:\images\",          // since you're already
                             @"e:\AFSImageMerge\" }; // going through e:\ ?
DateTime BeginningDate = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1);

foreach (string FolderPath in FoldersToLookAt)
{
    DirectoryInfo FilesToLookThrough = new DirectoryInfo(FolderPath);
    foreach (FileInfo MyFile in FilesToLookThrough.EnumerateFiles("*.dat",
                                                    SearchOption.AllDirectories))
    {
        if (MyFile.LastAccessTime >= BeginningDate)
        {
            Console.WriteLine(MyFile.FullName);
        }
    }
}

此代码不会循环几天,因此它应该具有与BeginningDate相关的常量运行时,这意味着无论您选择哪个日期,它都将花费相同的时间。

答案 2 :(得分:0)

听起来,给定要搜索的目录列表,查找要搜索的目录的某些子目录中与特定模式匹配的所有文件,并且已在当前月份内触摸过。鉴于问题陈述,这样的事情应该对你有用:

static IEnumerable<FileInfo> FindFiles( IEnumerable<string> directories , string searchPattern )
{
  DateTime     dtNow       = DateTime.Now.Date                ; // current date
  DateTime     dtFrom      = dtNow.AddDays( dtNow.Day - 1 )   ; // compute the first of the month @ start-of-day
  DateTime     dtThru      = dtFrom.AddMonths(1).AddTicks(-1) ; // compute the last of the month @ end-of-day
  string       childPattern = dtFrom.ToString( "yyMM*") ;

  return directories.Select( x => new DirectoryInfo( x ) )
                    .Where( x => x.Exists )
                    .SelectMany( x => x.EnumerateDirectories( childPattern , SearchOption.TopDirectoryOnly )
                                       .Where( subDir => {
                                           int dd ;
                                           int.TryParse( subDir.Name.Substring(4,2) , out dd ) ;
                                           return dd >= dtFrom.Day && dd <= dtThru.Day ;
                                         })
                    )
                    .SelectMany( subDir => subDir.EnumerateFiles( searchPattern , SearchOption.TopDirectoryOnly )
                                                 .Where( file => file.LastAccessTime >= dtFrom && file.LastAccessTime <= dtThru )
                    )
                    ;

}

解释此代码的作用:

directories.Select( x => new DirectoryInfo( x ) )

获取提供的字符串目录路径的可枚举列表,并将其转换为表示指定目录的DirectoryInfo个对象的可枚举列表

.Where( x => x.Exists )

排除任何不存在的目录

这允许使用要搜索的根目录集。

下一个条款有点复杂。 SelectMany()采用了可列举的列表。列表中的每个项目都会转换为可枚举的事物列表(可能与原始项目的对象类型相同或不同。但是,每个此类子列表必须属于同一类型。)

然后,生成的“列表列表”展平以生成单个可枚举列表。

考虑到这一点,

.SelectMany( x => x.EnumerateDirectories( childPattern , SearchOption.TopDirectoryOnly )
                   .Where( subDir => {
                     int dd ;
                     int.TryParse( subDir.Name.Substring(4,2) , out dd ) ;
                     return dd >= dtFrom.Day && dd <= dtThru.Day ;
                   })
                )

将每个根目录转换为子目录列表,其名称以指定的年份和月份(yyMM*)开头,其第4个&amp;第5个字符是该月的某一天。然后将该子目录列表列表展平为单个子目录列表。

最后SelectMany()

.SelectMany( subDir => subDir.EnumerateFiles( searchPattern , SearchOption.TopDirectoryOnly )
                             .Where( file => file.LastAccessTime >= dtFrom
                                          && file.LastAccessTime <= dtThru
                                   )
 )

遍历第一个SelectMany()生成的子目录列表,搜索每个名称与指定名称模式匹配的文件(示例中为*.dat)并且其最后访问时间在指定时间内帧。

然后,将得到的FileInfo对象列表列表展平为单个FileInfo对象列表,表示您感兴趣的文件。

然后您可以导演访问它们,例如

string[] searchDirs =
{ @"e:\"              ,
  @"e:\Kodak Images\" ,
  @"e:\images\"       ,
  @"e:\AFSImageMerge\"
} ;

foreach ( FileInfo fi in FindFiles( searchDirs , "*.dat" )
{
  do_something_with_interesting_file( fi ) ;
}