簡體   English   中英

帶日期過濾器的 C# GetFiles

[英]C# GetFiles with Date Filter

有沒有更有效的方法來從帶有日期過濾器的目錄中填充文件名列表?

目前,我正在這樣做:

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

這會遍歷文件夾中的每個文件,我覺得應該有一種更有效的方法。

第一個解決方案:

您可以使用 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

我認為您是在文件系統級別獲得更高的效率,而不是在 C# 級別。 如果是這種情況,答案是否定的:無法告訴文件系統按日期過濾。 它會不必要地返回一切。

如果您追求 CPU 效率:這是毫無意義的,因為將項目添加到列表框比按日期過濾要貴得多。 優化您的代碼不會產生任何結果。

我不想用正確的創建日期創建足夠的文件來做一個不錯的基准測試,所以我做了一個更通用的版本,它需要開始和結束時間並給出匹配的文件的名稱。 讓它給出昨天創建的文件的特定子字符串,自然而然地隨之而來。

我想出的最快的單線程純 .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);
    }
}

沒有IDisposable承諾的FindClose()是冒險的,並且IEnumerator<string>的手動實現不僅應該使這更容易(這樣做的嚴重原因)而且還希望減少 3 納秒或其他東西(不是這樣做的嚴重原因),但以上顯示了基本思想。

我用 :

DirectoryInfo dI = new DirectoryInfo(fileLocation); 
var files = dI.GetFiles().Where(i=>i.CreationTime>=dateFrom && i.CreationTime<=dateTo);

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM