20

我有一个包含数千个文件夹的基本目录。在这些文件夹中,可以有 1 到 20 个子文件夹,其中包含 1 到 10 个文件。我想删除所有超过 60 天的文件。我正在使用下面的代码来获取我必须删除的文件列表:

DirectoryInfo dirInfo = new DirectoryInfo(myBaseDirectory);
FileInfo[] oldFiles = 
  dirInfo.GetFiles("*.*", SearchOption.AllDirectories)
    .Where(t=>t.CreationTime < DateTime.Now.AddDays(-60)).ToArray();

但是我让它运行了大约 30 分钟,它仍然没有完成。我很好奇是否有人可以看到我可能会提高上述线路的性能,或者是否有不同的方式我应该完全接近这一点以获得更好的性能?建议?

4

7 回答 7

27

这(可能)与它将获得的一样好:

DateTime sixtyLess = DateTime.Now.AddDays(-60);
DirectoryInfo dirInfo = new DirectoryInfo(myBaseDirectory);
FileInfo[] oldFiles = 
    dirInfo.EnumerateFiles("*.*", SearchOption.AllDirectories)
           .AsParallel()
           .Where(fi => fi.CreationTime < sixtyLess).ToArray();

变化:

  • 使 60 天不那么DateTime恒定,因此减少了 CPU 负载。
  • 用过EnumerateFiles
  • 使查询并行。

应该在更短的时间内运行(不确定小多少)。

这是另一个可能比第一个更快或更慢的解决方案,它取决于数据:

DateTime sixtyLess = DateTime.Now.AddDays(-60);
DirectoryInfo dirInfo = new DirectoryInfo(myBaseDirectory);
FileInfo[] oldFiles = 
     dirInfo.EnumerateDirectories()
            .AsParallel()
            .SelectMany(di => di.EnumerateFiles("*.*", SearchOption.AllDirectories)
                                .Where(fi => fi.CreationTime < sixtyLess))
            .ToArray();

在这里,它将并行度移动到主文件夹枚举。上面的大多数更改也适用。

于 2013-07-19T22:12:44.287 回答
21

一种可能更快的替代方法是使用 WINAPI FindNextFile。为此,有一个出色的Faster Directory Enumeration Tool。可以按如下方式使用:

HashSet<FileData> GetPast60(string dir)
{
    DateTime retval = DateTime.Now.AddDays(-60);
    HashSet<FileData> oldFiles = new HashSet<FileData>();

    FileData [] files = FastDirectoryEnumerator.GetFiles(dir);
    for (int i=0; i<files.Length; i++)
    {
        if (files[i].LastWriteTime < retval)
        {
            oldFiles.Add(files[i]);
        }
    }    
    return oldFiles;
}

编辑

因此,根据下面的评论,我决定在这里以及我能想到的其他解决方案中对建议的解决方案进行基准测试。有趣的是,在 C# 中EnumerateFiles似乎优于 FindNextFile,而EnumerateFileswithAsParallel是迄今为止最快的,其次是命令提示符 count。但是请注意,AsParallel没有获得完整的文件计数或丢失了其他文件计数的一些文件,因此您可以说命令提示符方法是最好的

适用配置:

  • Windows 7 服务包 1 x64
  • Intel(R) Core(TM) i5-3210M CPU @2.50GHz 2.50GHz
  • 内存:6GB
  • 平台目标:x64
  • 无优化(注意:使用优化编译会产生极差的性能)
  • 允许不安全代码
  • 不调试就开始

下面是三个截图:

运行 1

运行 2

运行 3

我在下面包含了我的测试代码:

static void Main(string[] args)
{
    Console.Title = "File Enumeration Performance Comparison";
    Stopwatch watch = new Stopwatch();
    watch.Start();

    var allfiles = GetPast60("C:\\Users\\UserName\\Documents");
    watch.Stop();
    Console.WriteLine("Total time to enumerate using WINAPI =" + watch.ElapsedMilliseconds + "ms.");
    Console.WriteLine("File Count: " + allfiles);

    Stopwatch watch1 = new Stopwatch();
    watch1.Start();

    var allfiles1 = GetPast60Enum("C:\\Users\\UserName\\Documents\\");
    watch1.Stop();
    Console.WriteLine("Total time to enumerate using EnumerateFiles =" + watch1.ElapsedMilliseconds + "ms.");
    Console.WriteLine("File Count: " + allfiles1);

    Stopwatch watch2 = new Stopwatch();
    watch2.Start();

    var allfiles2 = Get1("C:\\Users\\UserName\\Documents\\");
    watch2.Stop();
    Console.WriteLine("Total time to enumerate using Get1 =" + watch2.ElapsedMilliseconds + "ms.");
    Console.WriteLine("File Count: " + allfiles2);


    Stopwatch watch3 = new Stopwatch();
    watch3.Start();

    var allfiles3 = Get2("C:\\Users\\UserName\\Documents\\");
    watch3.Stop();
    Console.WriteLine("Total time to enumerate using Get2 =" + watch3.ElapsedMilliseconds + "ms.");
    Console.WriteLine("File Count: " + allfiles3);

    Stopwatch watch4 = new Stopwatch();
    watch4.Start();

    var allfiles4 = RunCommand(@"dir /a: /b /s C:\Users\UserName\Documents");
    watch4.Stop();
    Console.WriteLine("Total time to enumerate using Command Prompt =" + watch4.ElapsedMilliseconds + "ms.");
    Console.WriteLine("File Count: " + allfiles4);


    Console.WriteLine("Press Any Key to Continue...");
    Console.ReadLine();
}

private static int RunCommand(string command)
{
    var process = new Process()
    {
        StartInfo = new ProcessStartInfo("cmd")
        {
            UseShellExecute = false,
            RedirectStandardInput = true,
            RedirectStandardOutput = true,
            CreateNoWindow = true,
            Arguments = String.Format("/c \"{0}\"", command),
        }
    };
    int count = 0;
    process.OutputDataReceived += delegate { count++; };
    process.Start();
    process.BeginOutputReadLine();

    process.WaitForExit();
    return count;
}

static int GetPast60Enum(string dir)
{
    return new DirectoryInfo(dir).EnumerateFiles("*.*", SearchOption.AllDirectories).Count();
}

private static int Get2(string myBaseDirectory)
{
    DirectoryInfo dirInfo = new DirectoryInfo(myBaseDirectory);
    return dirInfo.EnumerateFiles("*.*", SearchOption.AllDirectories)
               .AsParallel().Count();
}

private static int Get1(string myBaseDirectory)
{
    DirectoryInfo dirInfo = new DirectoryInfo(myBaseDirectory);
    return dirInfo.EnumerateDirectories()
               .AsParallel()
               .SelectMany(di => di.EnumerateFiles("*.*", SearchOption.AllDirectories))
               .Count() + dirInfo.EnumerateFiles("*.*", SearchOption.TopDirectoryOnly).Count();
}


private static int GetPast60(string dir)
{
    return FastDirectoryEnumerator.GetFiles(dir, "*.*", SearchOption.AllDirectories).Length;
}

注意:我专注于基准未修改日期的计数。

于 2013-07-19T22:36:51.723 回答
6

我意识到这对聚会来说已经很晚了,但是如果其他人正在寻找这个,那么您可以通过直接解析文件系统的 MFT 或 FAT 来将速度提高几个数量级,这需要管理员权限,因为我认为它会返回所有文件,无论安全性如何,但至少在枚举阶段可能会将您的 30 分钟缩短到 30 秒。

NTFS 库在这里https://github.com/LordMike/NtfsLib还有https://discutils.codeplex.com/我没有亲自使用过。

我只会将这些方法用于最初发现超过 x 天的文件,然后在删除之前验证它们,这可能有点矫枉过正,但我​​很谨慎。

于 2017-11-01T10:16:30.710 回答
3

上面答案(#itsnotalie & #Chibueze Opata)中的 Get1 方法缺少计算根目录中的文件,所以它应该是:

private static int Get1(string myBaseDirectory)
{
    DirectoryInfo dirInfo = new DirectoryInfo(myBaseDirectory);
    return dirInfo.EnumerateDirectories()
               .AsParallel()
               .SelectMany(di => di.EnumerateFiles("*.*", SearchOption.AllDirectories))
               .Count() + dirInfo.EnumerateFiles("*.*", SearchOption.TopDirectoryOnly).Count();
}
于 2017-07-15T18:08:27.427 回答
0

您正在使用 Linq。如果您编写自己的方法在特殊情况下递归搜索目录会更快。

public static DateTime retval = DateTime.Now.AddDays(-60);

public static void WalkDirectoryTree(System.IO.DirectoryInfo root)
{
    System.IO.FileInfo[] files = null;
    System.IO.DirectoryInfo[] subDirs = null;

    // First, process all the files directly under this folder 
    try
    {
        files = root.GetFiles("*.*");
    }
    // This is thrown if even one of the files requires permissions greater 
    // than the application provides. 
    catch (UnauthorizedAccessException e)
    {
        // This code just writes out the message and continues to recurse. 
        // You may decide to do something different here. For example, you 
        // can try to elevate your privileges and access the file again.
        log.Add(e.Message);
    }
    catch (System.IO.DirectoryNotFoundException e)
    {
        Console.WriteLine(e.Message);
    }

    if (files != null)
    {
        foreach (System.IO.FileInfo fi in files)
        {
          if (fi.LastWriteTime < retval)
          {
            oldFiles.Add(files[i]);
          }

            Console.WriteLine(fi.FullName);
        }

        // Now find all the subdirectories under this directory.
        subDirs = root.GetDirectories();

        foreach (System.IO.DirectoryInfo dirInfo in subDirs)
        {
            // Resursive call for each subdirectory.
            WalkDirectoryTree(dirInfo);
        }
    }            
}
于 2013-07-19T22:42:06.477 回答
0

如果您真的想提高性能,请亲自动手并使用NtQueryDirectoryFileWindows 内部的缓冲区大小较大的缓冲区。

FindFirstFile已经很慢了,虽然FindFirstFileEx稍微好一点,但最好的性能将来自直接调用本机函数。

于 2013-07-20T02:10:47.920 回答
0

使用时SearchOption.AllDirectories EnumerateFiles需要很长时间才能返回第一个项目。在这里阅读了几个很好的答案后,我现在已经完成了以下功能。通过一次只在一个目录上工作并递归调用它,它现在几乎立即返回第一项。但我必须承认,我并不完全确定正确的使用方法,.AsParallel()所以不要盲目使用。

我强烈建议不要使用数组,而是使用枚举。有人提到磁盘速度是限制因素,线程无济于事,就总时间而言,只要操作系统没有缓存任何内容,就很可能,但是通过使用多个线程,您可以获得首先返回的缓存数据,而否则可能会修剪缓存以为新结果腾出空间。

递归调用可能会影响堆栈,但大多数 FS 对可以有多少级别是有限制的,因此不应该成为一个真正的问题。

    private static IEnumerable<FileInfo> EnumerateFilesParallel(DirectoryInfo dir)
    {
        return dir.EnumerateDirectories()
            .AsParallel()
            .SelectMany(EnumerateFilesParallel)
            .Concat(dir.EnumerateFiles("*", SearchOption.TopDirectoryOnly).AsParallel());
    }
于 2019-10-20T23:12:54.360 回答