【问题标题】:Get all files and directories in specific path fast快速获取特定路径中的所有文件和目录
【发布时间】:2016-02-19 11:54:02
【问题描述】:

我正在创建一个 c# 扫描目录的备份应用程序。在我使用这样的东西来获取目录中的所有文件和子文件之前:

DirectoryInfo di = new DirectoryInfo("A:\\");
var directories= di.GetFiles("*", SearchOption.AllDirectories);

foreach (FileInfo d in directories)
{
       //Add files to a list so that later they can be compared to see if each file
       // needs to be copid or not
}

唯一的问题是有时无法访问文件并且我遇到了几个错误。我得到的错误示例是:

因此,我创建了一个递归方法,它将扫描当前目录中的所有文件。如果该目录中有目录,则将再次调用该方法并传递该目录。这种方法的好处是我可以将文件放在 try catch 块中,如果没有错误,我可以选择将这些文件添加到列表中,如果有错误,我可以将目录添加到另一个列表中。

try
{
    files = di.GetFiles(searchPattern, SearchOption.TopDirectoryOnly);               
}
catch
{
     //info of this folder was not able to get
     lstFilesErrors.Add(sDir(di));
     return;
}

所以这个方法很好用,唯一的问题是当我扫描一个大目录时需要很多次。我怎样才能加快这个过程?我的实际方法是这样,以备不时之需。

private void startScan(DirectoryInfo di)
{
    //lstFilesErrors is a list of MyFile objects
    // I created that class because I wanted to store more specific information
    // about a file such as its comparePath name and other properties that I need 
    // in order to compare it with another list

    // lstFiles is a list of MyFile objects that store all the files
    // that are contained in path that I want to scan

    FileInfo[] files = null;
    DirectoryInfo[] directories = null;
    string searchPattern = "*.*";

    try
    {
        files = di.GetFiles(searchPattern, SearchOption.TopDirectoryOnly);               
    }
    catch
    {
        //info of this folder was not able to get
        lstFilesErrors.Add(sDir(di));
        return;
    }

    // if there are files in the directory then add those files to the list
    if (files != null)
    {
        foreach (FileInfo f in files)
        {
            lstFiles.Add(sFile(f));
        }
    }


    try
    {
        directories = di.GetDirectories(searchPattern, SearchOption.TopDirectoryOnly);
    }
    catch
    {
        lstFilesErrors.Add(sDir(di));
        return;
    }

    // if that directory has more directories then add them to the list then 
    // execute this function
    if (directories != null)
        foreach (DirectoryInfo d in directories)
        {
            FileInfo[] subFiles = null;
            DirectoryInfo[] subDir = null;

            bool isThereAnError = false;

            try
            {
                subFiles = d.GetFiles();
                subDir = d.GetDirectories();

            }
            catch
            {
                isThereAnError = true;                                                
            }

            if (isThereAnError)
                lstFilesErrors.Add(sDir(d));
            else
            {
                lstFiles.Add(sDir(d));
                startScan(d);
            }


        }

}

如果我尝试使用以下方式处理异常,则会出现问题:

DirectoryInfo di = new DirectoryInfo("A:\\");
FileInfo[] directories = null;
            try
            {
                directories = di.GetFiles("*", SearchOption.AllDirectories);

            }
            catch (UnauthorizedAccessException e)
            {
                Console.WriteLine("There was an error with UnauthorizedAccessException");
            }
            catch
            {
                Console.WriteLine("There was antother error");
            }

如果发生异常,我就没有文件了。

【问题讨论】:

  • 您应该只捕获特定的异常(例如UnauthorisedAccessException),而不是捕获所有内容,否则编程(例如NullReferenceException)和系统错误(例如OutOfMemoryException)将被屏蔽为应用程序错误.
  • 所需时间取决于层次结构中的文件数量。如果有很多文件,这将需要很长时间。就是这样。
  • 顺便说一下,我在这里展示了一个更简单的递归目录列表方法:informit.com/guides/content.aspx?g=dotnet&seqNum=159。您可以修改该代码以处理异常并将内容存储在您的列表中。

标签: c# performance fileinfo


【解决方案1】:

这种方法要快得多。只有在目录中放置大量文件时才能拨打电话。我的 A:\ 外置硬盘几乎包含 1 TB,因此在处理大量文件时会产生很大的不同。

static void Main(string[] args)
{
    DirectoryInfo di = new DirectoryInfo("A:\\");
    FullDirList(di, "*");
    Console.WriteLine("Done");
    Console.Read();
}

static List<FileInfo> files = new List<FileInfo>();  // List that will hold the files and subfiles in path
static List<DirectoryInfo> folders = new List<DirectoryInfo>(); // List that hold direcotries that cannot be accessed
static void FullDirList(DirectoryInfo dir, string searchPattern)
{
    // Console.WriteLine("Directory {0}", dir.FullName);
    // list the files
    try
    {
        foreach (FileInfo f in dir.GetFiles(searchPattern))
        {
            //Console.WriteLine("File {0}", f.FullName);
            files.Add(f);                    
        }
    }
    catch
    {
        Console.WriteLine("Directory {0}  \n could not be accessed!!!!", dir.FullName);                
        return;  // We alredy got an error trying to access dir so dont try to access it again
    }

    // process each directory
    // If I have been able to see the files in the directory I should also be able 
    // to look at its directories so I dont think I should place this in a try catch block
    foreach (DirectoryInfo d in dir.GetDirectories())
    {
        folders.Add(d);
        FullDirList(d, searchPattern);                    
    }

}

顺便说一句,感谢您的评论吉姆·米歇尔(Jim Mischel)

【讨论】:

  • 谢谢。这种方法比 Directory.GetFileSystemEntries 快 100 倍
【解决方案2】:

在 .NET 4.0 中有一个 Directory.EnumerateFiles 方法,它返回一个 IEnumerable&lt;string&gt; 并且不会将所有文件加载到内存中。只有在您开始迭代返回的集合时,才会返回文件和exceptions could be handled

【讨论】:

  • 这很好,但如果我遇到异常,查询会停止,除非我做错了什么
【解决方案3】:

.NET 文件枚举方法缓慢的历史由来已久。问题是没有一种即时的方式来枚举大型目录结构。即使这里接受的答案也存在 GC 分配问题。

我能做的最好的事情就是包装在我的库中,并作为CSharpTest.Net.IO 命名空间中的FindFile (source) 类公开。此类可以枚举文件和文件夹,而无需进行不必要的 GC 分配和字符串编组。

用法很简单,RaiseOnAccessDenied 属性会跳过用户无权访问的目录和文件:

    private static long SizeOf(string directory)
    {
        var fcounter = new CSharpTest.Net.IO.FindFile(directory, "*", true, true, true);
        fcounter.RaiseOnAccessDenied = false;

        long size = 0, total = 0;
        fcounter.FileFound +=
            (o, e) =>
            {
                if (!e.IsDirectory)
                {
                    Interlocked.Increment(ref total);
                    size += e.Length;
                }
            };

        Stopwatch sw = Stopwatch.StartNew();
        fcounter.Find();
        Console.WriteLine("Enumerated {0:n0} files totaling {1:n0} bytes in {2:n3} seconds.",
                          total, size, sw.Elapsed.TotalSeconds);
        return size;
    }

对于我的本地 C:\ 驱动器,此输出如下:

在 232.876 秒内枚举了 810,046 个文件,总计 307,707,792,662 个字节。

您的里程可能会因驱动器速度而异,但这是我发现的在托管代码中枚举文件的最快方法。 event 参数是 FindFile.FileFoundEventArgs 类型的变异类,因此请确保不要保留对它的引用,因为它的值会随着每个引发的事件而改变。

【讨论】:

  • +1,因为与其他技术相比,它的速度令人印象深刻。THE ONLY PROBLEM IS THAT IT FINDS LESS FILES THAN THE OTHER ALGORITHMS WHEN USING IT AGAINS THE C DRIVE
  • 您也缺少调用FIND() 方法。我在 lambda 之后放置了 fcounter.Find() 方法,效果很好。
  • 哦,废话...大声笑是的,样本被窃听了;)感谢您指出
  • 我需要找到具有多种搜索模式的特定文件。我应该如何添加它们来代替“”@csharptest.net...我尝试了“.txt;*.exe”、“.txt|.exe”。
  • @gotoVoid 只是枚举 . 并自己过滤扩展。这样实际上更快。
【解决方案4】:

我知道这是旧的,但是...另一种选择可能是像这样使用 FileSystemWatcher:

void SomeMethod()
{
    System.IO.FileSystemWatcher m_Watcher = new System.IO.FileSystemWatcher();
    m_Watcher.Path = path;
    m_Watcher.Filter = "*.*";
    m_Watcher.NotifyFilter = m_Watcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;
    m_Watcher.Created += new FileSystemEventHandler(OnChanged);
    m_Watcher.EnableRaisingEvents = true;
}

private void OnChanged(object sender, FileSystemEventArgs e)
    {
        string path = e.FullPath;

        lock (listLock)
        {
            pathsToUpload.Add(path);
        }
    }

这将允许您通过一个极其轻量级的过程来监视目录中的文件更改,然后您可以使用它来存储更​​改的文件的名称,以便您可以在适当的时间备份它们。

【讨论】:

    【解决方案5】:

    (从我在你的另一个问题中的另一个答案中复制了这篇文章)

    Show progress when searching all files in a directory

    快速文件枚举

    当然,正如您已经知道的那样,有很多方法可以进行枚举本身......但没有一种方法是即时的。您可以尝试使用文件系统的USN Journal 进行扫描。看看 CodePlex 中的这个项目:MFT Scanner in VB.NET...它在不到 15 秒的时间内找到了我的 IDE SATA(不是 SSD)驱动器中的所有文件,并找到了 311000 个文件。

    您必须按路径过滤文件,以便只返回您正在查找的路径内的文件。但这是工作中最简单的部分!

    【讨论】:

    • 这似乎需要管理员提升,否则传递new DriveInfo("c") 会导致ACCESS_DENIED 异常。它也仅限于启用了日志的 NTFS 分区。好的解决方案,否则它肯定比使用普通 API 快很多。不知道为什么核心框架不使用它或者为什么文件系统不提供任何访问级别都可以访问的只读版本。
    • @SamuelJackson 当使用 Journal 枚举 MFT 条目时,所有更改都会列出,我的意思是,即使是其他用户、管理员或系统本身所做的更改。一切!这就是为什么需要的访问级别是备份操作员...允许从文件系统读取任何内容,但不能执行,也不能写入不属于他/她自己的文件。
    【解决方案6】:

    也许对你有帮助。 您可以使用“DirectoryInfo.EnumerateFiles”方法并根据需要处理 UnauthorizedAccessException

    using System;
    using System.IO;
    
    class Program
    {
        static void Main(string[] args)
        {
            DirectoryInfo diTop = new DirectoryInfo(@"d:\");
            try
            {
                foreach (var fi in diTop.EnumerateFiles())
                {
                    try
                    {
                        // Display each file over 10 MB; 
                        if (fi.Length > 10000000)
                        {
                            Console.WriteLine("{0}\t\t{1}", fi.FullName, fi.Length.ToString("N0"));
                        }
                    }
                    catch (UnauthorizedAccessException UnAuthTop)
                    {
                        Console.WriteLine("{0}", UnAuthTop.Message);
                    }
                }
    
                foreach (var di in diTop.EnumerateDirectories("*"))
                {
                    try
                    {
                        foreach (var fi in di.EnumerateFiles("*", SearchOption.AllDirectories))
                        {
                            try
                            {
                                // Display each file over 10 MB; 
                                if (fi.Length > 10000000)
                                {
                                    Console.WriteLine("{0}\t\t{1}",  fi.FullName, fi.Length.ToString("N0"));
                                }
                            }
                            catch (UnauthorizedAccessException UnAuthFile)
                            {
                                Console.WriteLine("UnAuthFile: {0}", UnAuthFile.Message);
                            }
                        }
                    }
                    catch (UnauthorizedAccessException UnAuthSubDir)
                    {
                        Console.WriteLine("UnAuthSubDir: {0}", UnAuthSubDir.Message);
                    }
                }
            }
            catch (DirectoryNotFoundException DirNotFound)
            {
                Console.WriteLine("{0}", DirNotFound.Message);
            }
            catch (UnauthorizedAccessException UnAuthDir)
            {
                Console.WriteLine("UnAuthDir: {0}", UnAuthDir.Message);
            }
            catch (PathTooLongException LongPath)
            {
                Console.WriteLine("{0}", LongPath.Message);
            }
        }
    }
    

    【讨论】:

      【解决方案7】:

      您可以使用它来获取所有目录和子目录。然后简单地循环处理文件。

      string[] folders = System.IO.Directory.GetDirectories(@"C:\My Sample Path\","*", System.IO.SearchOption.AllDirectories);
      
      foreach(string f in folders)
      {
         //call some function to get all files in folder
      }
      

      【讨论】:

      • 题主好像没看懂
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-04-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-11-18
      • 1970-01-01
      • 2016-02-20
      相关资源
      最近更新 更多