10

日付フィルターを使用して、ディレクトリからファイル名のリストを作成するより効率的な方法はありますか?

現在、私はこれをやっています:

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

これはフォルダー内のすべてのファイルを通過しますが、もっと効率的な方法が必要だと思います。

4

4 に答える 4

21

最初の解決策:

LINQ を使用できます。

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

次に、この名前のリストを直接使用できます。

2 番目の解決策:

高速化する別の解決策は次のとおりです。

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
于 2012-08-13T19:57:42.087 に答える
9

C# レベルではなく、ファイル システム レベルで効率を上げた後だと思います。その場合、答えは「いいえ」です。ファイル システムに日付でフィルタリングするように指示する方法はありません。不必要にすべてを返します。

CPU 効率を求めている場合: リストボックスにアイテムを追加することは、日付でフィルター処理するよりも非常にコストがかかるため、これは無意味です。コードを最適化しても結果は得られません。

于 2012-08-13T22:30:28.387 に答える
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 ナノ秒か何かのように削り取る必要があります (それを行う重大な理由ではありません)。 、しかし、上記は基本的な考え方を示しています。

于 2012-08-14T00:38:43.470 に答える
-1

私が使う :

DirectoryInfo dI = new DirectoryInfo(fileLocation); 
var files = dI.GetFiles().Where(i=>i.CreationTime>=dateFrom && i.CreationTime<=dateTo);
于 2020-06-01T13:47:49.860 に答える