3

我正在编写一个程序,当给定日期、文件夹路径和文件扩展名时,它将查看文件夹并查找从月初到当前日期具有最后访问时间的所有文件,并且只有文件使用传递的文件扩展名。

我要查找的文件始终位于文件夹树中的同一级别,因此我可以在程序中编写代码,挖掘多远才能找到文件。

目前我的程序一天大约需要一分钟,所以今天(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# 中是一种从文件中提取信息而不将其存储在列表中的方法吗?还是我必须从头开始构建程序?

4

3 回答 3

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() 时创建一个新缓冲区并将它们全部复制一次。

于 2013-04-16T22:01:03.257 回答
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,这意味着无论你选择什么日期,它总是需要相同的时间。

于 2013-04-16T21:35:48.873 回答
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 和第 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 ) ;
}
于 2013-04-16T22:15:23.007 回答