3

私は、日付、フォルダー パス、およびファイル拡張子を指定すると、フォルダーを調べて、月初から現在の日付までの最終アクセス時刻を持つすべてのファイルを検索し、ファイルのみを検索するプログラムを作成しています。渡されたファイル拡張子を付けます。

探しているファイルは常にフォルダー ツリーの同じレベルにあるため、どこまで掘り下げてファイルを見つけるかをプログラムにコーディングできます。

現在、私のプログラムは 1 日約 1 分かかるため、今日 (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# の方法はありますか? それとも、プログラムを最初から作成する必要がありますか?

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 をカーネル モードに切り替えたり戻したりする必要があり、非常に遅いです。はるかに大きなテキストのチャンクを含むイベントを 1 回実行すると、何度も実行するよりもはるかに高速になります。さて、古き良きことをするのではなく、StringBuilder を使用する理由は次のとおりです。

string output;

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

C# では、2 つの文字列を相互に追加すると、新しい文字列が作成されます。特に新しい文字列が大きくなるにつれて、これを何度も行うのも遅くなります。StringBuilder は、すべての文字列のリストを維持するだけで、ToString() を呼び出すと、新しいバッファーを作成してそれらをすべて一度にコピーします。

于 2013-04-16T22:01:03.257 に答える
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 に答える