【问题标题】:Checking if folder has files检查文件夹是否有文件
【发布时间】:2011-02-12 08:34:38
【问题描述】:

我有程序写入数据库,哪些文件夹已满或为空。现在我正在使用

bool hasFiles=false;
(Directory.GetFiles(path).Length >0) ? hasFiles=true: hasFiles=false;

但这需要将近一个小时,这段时间我什么都做不了。

有没有最快的方法来检查文件夹是否有文件?

【问题讨论】:

  • 需要一个小时的“它”是什么?这行特定的代码,还是在磁盘上数千个目录的循环中使用它?
  • @Hans Kesting 大约 30k 个文件夹
  • @phenevo - 你检查过什么需要时间吗?它是查询文件系统,还是(更有可能)写入数据库?
  • @Marc Gravell 我检查了。这是文件夹的问题。从数据库中读取速度很快。
  • 啊,你是在网络上做的……这肯定会导致文件数量变慢。那是联网的外部驱动器还是服务器?

标签: c# directory


【解决方案1】:

要检查目录或子目录中是否存在任何文件,在.net 4中,您可以使用以下方法:

public bool isDirectoryContainFiles(string path) {
    if (!Directory.Exists(path)) return false;
    return Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories).Any();
}

【讨论】:

    【解决方案2】:

    加快这种跨网络搜索的关键是减少跨网络的请求数量。与其获取所有目录,然后检查每个目录的文件,不如尝试一次调用获取所有内容。

    在 .NET 3.5 中,没有一种方法可以递归地获取所有文件和文件夹,因此您必须自己构建它(见下文)。在 .NET 4 中,新的重载可以一步到位。

    使用DirectoryInfo 还可以获取有关返回名称是文件还是目录的信息,这也减少了调用。

    这意味着拆分所有目录和文件的列表变成这样:

    struct AllDirectories {
      public List<string> DirectoriesWithoutFiles { get; set; }
      public List<string> DirectoriesWithFiles { get; set; }
    }
    
    static class FileSystemScanner {
      public AllDirectories DivideDirectories(string startingPath) {
        var startingDir = new DirectoryInfo(startingPath);
    
        // allContent IList<FileSystemInfo>
        var allContent = GetAllFileSystemObjects(startingDir);
        var allFiles = allContent.Where(f => !(f.Attributes & FileAttributes.Directory))
                                 .Cast<FileInfo>();
        var dirs = allContent.Where(f => (f.Attributes & FileAttributes.Directory))
                             .Cast<DirectoryInfo>();
        var allDirs = new SortedList<DirectoryInfo>(dirs, new FileSystemInfoComparer());
    
        var res = new AllDirectories {
          DirectoriesWithFiles = new List<string>()
        };
        foreach (var file in allFiles) {
          var dirName = Path.GetDirectoryName(file.Name);
          if (allDirs.Remove(dirName)) {
            // Was removed, so first time this dir name seen.
            res.DirectoriesWithFiles.Add(dirName);
          }
        }
        // allDirs now just contains directories without files
        res.DirectoriesWithoutFiles = new List<String>(addDirs.Select(d => d.Name));
      }
    
      class FileSystemInfoComparer : IComparer<FileSystemInfo> {
        public int Compare(FileSystemInfo l, FileSystemInfo r) {
          return String.Compare(l.Name, r.Name, StringComparison.OrdinalIgnoreCase);
        }
      }
    }
    

    实现 GetAllFileSystemObjects 取决于 .NET 版本。在 .NET 4 上这很容易:

    ILIst<FileSystemInfo> GetAllFileSystemObjects(DirectoryInfo root) {
      return root.GetFileSystemInfos("*.*", SearchOptions.AllDirectories);
    }
    

    在早期版本中还需要做更多的工作:

    ILIst<FileSystemInfo> GetAllFileSystemObjects(DirectoryInfo root) {
      var res = new List<FileSystemInfo>();
      var pending = new Queue<DirectoryInfo>(new [] { root });
    
      while (pending.Count > 0) {
        var dir = pending.Dequeue();
        var content = dir.GetFileSystemInfos();
        res.AddRange(content);
        foreach (var dir in content.Where(f => (f.Attributes & FileAttributes.Directory))
                                   .Cast<DirectoryInfo>()) {
          pending.Enqueue(dir);
        }
      }
    
      return res;
    }
    

    这种方法尽可能少地调用文件系统,在 .NET 4 上只调用一次,或者在早期版本中每个目录调用一次,从而使网络客户端和服务器能够最大限度地减少底层文件系统调用和网络往返的次数。

    获取FileSystemInfo 实例的缺点是需要多个文件系统操作(我相信这在一定程度上取决于操作系统),但是对于每个名称,任何解决方案都需要知道它是文件还是目录,因此在某些情况下这是无法避免的级别(不诉诸 FindFileFirst/FindNextFile/FindClose 的 P/Invoke)。


    除此之外,使用分区扩展方法会更容易:

    Tuple<IEnumerable<T>,IEnumerable<T>> Extensions.Partition<T>(
                                                     this IEnumerable<T> input,
                                                     Func<T,bool> parition);
    

    把它写成懒惰是一个有趣的练习(只有在某些东西迭代一个输出时才消耗输入,同时缓冲另一个输出)。

    【讨论】:

    • 需要类似的东西,但只是想知道。当您使用addDirs 变量时,我猜您的意思是allDirs?还是我错过了什么?
    • @Niklas 可能。 (但已经有几年了……)请记住,您在 .NET 4 中不需要此代码,因为它可以递归地读取文件和目录。
    【解决方案3】:

    如果您使用的是 .Net 4.0,请查看 EnumerateFiles 方法。 http://msdn.microsoft.com/en-us/library/dd413232(v=VS.100).aspx

    EnumerateFiles 和 GetFiles 方法不同如下:当您 使用 EnumerateFiles,你可以开始 枚举 FileInfo 的集合 整个集合之前的对象 回来;当你使用 GetFiles 时,你 必须等待整个数组 要返回的 FileInfo 对象 在您可以访问阵列之前。 因此,当您与 许多文件和目录, EnumerateFiles 可以更高效。

    这种方式不会从文件夹中检索所有文件,如果枚举器至少有 1 个文件,则文件夹不为空

    【讨论】:

      【解决方案4】:

      我假设(尽管我不确定)因为您在网络驱动器上调用 GetFiles() 会增加相当多的时间来从所有 30k 文件夹中检索所有文件并枚举它们。

      我在 CodeProject 上找到了一个替代目录枚举器 here,看起来很有希望。

      或者...您可以在服务器上创建一个 WebService,为您枚举所有内容并在之后返回结果。

      编辑:我认为您的问题更有可能是文件夹访问。每次您访问网络驱动器中的目录时,您都会遇到安全和权限检查。 * 30k 个文件夹将对性能造成很大影响。我非常怀疑使用 FindFirstFile 是否会有很大帮助,因为枚举的实际文件数只会是 0 或 1。

      【讨论】:

        【解决方案5】:

        可能值得一提:

        但这需要将近一个小时,而且这段时间我什么也做不了。 (强调)

        您是在 GUI 应用程序的主线程上执行此操作吗?如果是这样,请使用BackgroundWorker 取消此过程。至少,该应用程序将继续响应。您还可以在方法中添加对 CancellationPending 的检查,如果花费的时间过长则取消它。

        有点与您的问题无关——只是我注意到并认为我会发表评论的事情。

        【讨论】:

          【解决方案6】:

          最好的办法是使用 API 函数 FindFirstFile。到时候就不会花这么长的时间了。

          【讨论】:

          • 每个文件夹只有一个文件;问题看起来是大量远程文件夹,按顺序访问。
          • +1 这是一个讨论,有人发现 FindFirstfile 比 Directories.GetFiles 检查空目录要快得多,因此值得一试:stackoverflow.com/questions/755574/…
          • 我同意 Marc 的观点。问题不在于枚举文件,而是枚举并逐步遍历所有文件夹结构。每次 .Net 在目录上调用 GetFiles() 时,每次目录尝试访问它时都会进行一系列安全检查。
          猜你喜欢
          • 2022-01-23
          • 2010-11-08
          • 1970-01-01
          • 2012-02-05
          • 1970-01-01
          • 2020-08-20
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多