如何快速检查文件夹是不是为空(.NET)?

Posted

技术标签:

【中文标题】如何快速检查文件夹是不是为空(.NET)?【英文标题】:How to quickly check if folder is empty (.NET)?如何快速检查文件夹是否为空(.NET)? 【发布时间】:2010-10-19 19:50:35 【问题描述】:

我必须检查磁盘上的目录是否为空。这意味着,它不包含任何文件夹/文件。我知道,有一个简单的方法。我们得到 FileSystemInfo 的数组并检查元素的数量是否为零。类似的东西:

public static bool CheckFolderEmpty(string path)

    if (string.IsNullOrEmpty(path))
    
        throw new ArgumentNullException("path");
    

    var folder = new DirectoryInfo(path);
    if (folder.Exists)
    
        return folder.GetFileSystemInfos().Length == 0;
    

    throw new DirectoryNotFoundException();

这种方法看起来不错。但!!从性能的角度来看,这是非常非常糟糕的。 GetFileSystemInfos() 是一个非常难的方法。实际上,它枚举文件夹的所有文件系统对象,获取它们的所有属性,创建对象,填充类型数组等。所有这些只是为了简单地检查长度。这很愚蠢,不是吗?

我刚刚分析了这样的代码并确定,大约 250 次这样的方法调用在大约 500 毫秒内执行。这非常慢,我相信可以更快地完成。

有什么建议吗?

【问题讨论】:

出于好奇,您为什么要检查目录 250 次? @ya23 我想有人想检查 250 个不同的目录。没有一个 250 次。 【参考方案1】:

.NET 4 中的 DirectoryDirectoryInfo 中有一个新功能,允许它们返回 IEnumerable 而不是数组,并在读取所有目录内容之前开始返回结果。

What's New in the BCL in .NET 4 Beta 1 Directory.EnumerateFileSystemEntries method overloads
public bool IsDirectoryEmpty(string path)

    IEnumerable<string> items = Directory.EnumerateFileSystemEntries(path);
    using (IEnumerator<string> en = items.GetEnumerator())
    
        return !en.MoveNext();
    


编辑:再次看到那个答案,我意识到这段代码可以变得更简单......

public bool IsDirectoryEmpty(string path)

    return !Directory.EnumerateFileSystemEntries(path).Any();

【讨论】:

我喜欢这个解决方案,可以让它只检查某些文件类型吗? .Contains("jpg") 而不是 .any() 似乎不起作用 @Dennis,您可以在对EnumerateFileSystemEntries 的调用中指定通配符模式,或者使用.Any(condition)(将条件指定为lambda 表达式,或者作为将路径作为参数的方法)。 可以从第一个代码示例中删除类型转换:return !items.GetEnumerator().MoveNext(); @gary,如果你这样做,枚举器将不会被释放,所以它会锁定目录,直到枚举器被垃圾回收。 这似乎适用于包含文件的目录,但如果目录包含其他目录,它会返回说它是空的。【参考方案2】:

这是我最终实施的超快速解决方案。这里我使用 WinAPI 和函数 FindFirstFileFindNextFile。它允许避免枚举文件夹中的所有项目并在检测到文件夹中的第一个对象后立即停止。这种方法比上面描述的要快 6(!!) 倍。 36 毫秒内调用 250 次!

private static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
private struct WIN32_FIND_DATA

    public uint dwFileAttributes;
    public System.Runtime.InteropServices.ComTypes.FILETIME ftCreationTime;
    public System.Runtime.InteropServices.ComTypes.FILETIME ftLastAccessTime;
    public System.Runtime.InteropServices.ComTypes.FILETIME ftLastWriteTime;
    public uint nFileSizeHigh;
    public uint nFileSizeLow;
    public uint dwReserved0;
    public uint dwReserved1;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
    public string cFileName;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
    public string cAlternateFileName;


[DllImport("kernel32.dll", CharSet=CharSet.Auto)]
private static extern IntPtr FindFirstFile(string lpFileName, out WIN32_FIND_DATA lpFindFileData);

[DllImport("kernel32.dll", CharSet=CharSet.Auto)]
private static extern bool FindNextFile(IntPtr hFindFile, out WIN32_FIND_DATA lpFindFileData);

[DllImport("kernel32.dll")]
private static extern bool FindClose(IntPtr hFindFile);

public static bool CheckDirectoryEmpty_Fast(string path)

    if (string.IsNullOrEmpty(path))
    
        throw new ArgumentNullException(path);
    

    if (Directory.Exists(path))
    
        if (path.EndsWith(Path.DirectorySeparatorChar.ToString()))
            path += "*";
        else
            path += Path.DirectorySeparatorChar + "*";

        WIN32_FIND_DATA findData;
        var findHandle = FindFirstFile(path, out findData);

        if (findHandle != INVALID_HANDLE_VALUE)
        
            try
            
                bool empty = true;
                do
                
                    if (findData.cFileName != "." && findData.cFileName != "..")
                        empty = false;
                 while (empty && FindNextFile(findHandle, out findData));

                return empty;
            
            finally
            
                FindClose(findHandle);
            
        

        throw new Exception("Failed to get directory first file",
            Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error()));
    
    throw new DirectoryNotFoundException();

我希望它对将来的某些人有用。

【讨论】:

感谢您分享您的解决方案。 您需要将SetLastError = true 添加到DllImport for FindFirstFile 以使Marshal.GetHRForLastWin32Error() 调用正常工作,如MSDN doc for GetHRForLastWin32Error() 的备注部分所述。跨度> 我认为下面的答案好一点,因为它还在子目录***.com/questions/724148/… 中查找文件【参考方案3】:

您可以尝试 Directory.Exists(path)Directory.GetFiles(path) - 开销可能更少(没有对象 - 只是字符串等)。

【讨论】:

一如既往,您是最快的!在那里打败我几秒钟! :-) 你们俩都比我快...该死我对细节的关注;-) 不过对我没有任何好处;第一个答案,也是唯一没有投票的答案;-( 未修复...有人有斧头要磨,我想 我认为 GetFiles 不会获得目录列表,因此检查 GetDirectories 似乎是个好主意【参考方案4】:
private static void test()

    System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
    sw.Start();

    string [] dirs = System.IO.Directory.GetDirectories("C:\\Test\\");
    string[] files = System.IO.Directory.GetFiles("C:\\Test\\");

    if (dirs.Length == 0 && files.Length == 0)
        Console.WriteLine("Empty");
    else
        Console.WriteLine("Not Empty");

    sw.Stop();
    Console.WriteLine(sw.ElapsedMilliseconds);

当文件夹为空且包含子文件夹和文件(5 个文件夹,每个文件夹 5 个文件)时,此快速测试在 2 毫秒内返回

【讨论】:

如果 'dirs' 不为空,您可以通过立即返回来改进这一点,而无需获取文件列表。 是的,但是如果里面有数千个文件呢? 您还测量了写入控制台的时间,这是不可忽略的。【参考方案5】:

我把它用于文件夹和文件(不知道是否最佳)

    if(Directory.GetFileSystemEntries(path).Length == 0)

【讨论】:

【参考方案6】:

如果您不介意离开纯 C# 并进行 WinApi 调用,那么您可能需要考虑 PathIsDirectoryEmpty() 函数。根据MSDN,功能:

如果 pszPath 是空目录,则返回 TRUE。如果 pszPath 不是目录,或者它至少包含一个除“.”以外的文件,则返回 FALSE或“..”。

这似乎是一个完全符合您要求的功能,因此它可能已针对该任务进行了很好的优化(尽管我尚未对此进行测试)。

要从 C# 调用它,pinvoke.net 站点应该可以帮助您。 (不幸的是,它还没有描述这个特定的函数,但是你应该能够找到一些具有相似参数和返回类型的函数,并将它们用作调用的基础。如果你再次查看 MSDN,它会说要导入的 DLL 是 shlwapi.dll)

【讨论】:

好主意。我不知道这个功能。我将尝试将它的性能与我上面描述的方法进行比较。如果它做得更快,我会在我的代码中重用它。谢谢。 给那些想要走这条路的人的说明。似乎 shlwapi.dll 中的 PathIsDirectoryEmpty() 方法在 Vista32/64 和 XP32/64 机器上运行良好,但在某些 Win7 机器上运行良好。这一定与不同版本的 Windows 附带的 shlwapi.dll 版本有关。当心。【参考方案7】:

我不知道这个的性能统计,但是你试过使用Directory.GetFiles()静态方法吗?

它返回一个包含文件名(不是 FileInfos)的字符串数组,您可以按照与上述相同的方式检查数组的长度。

【讨论】:

同样的问题,如果有很多文件,它可能会很慢......但它可能更快 GetFileSystemInfos【参考方案8】:

我确定其他答案更快,并且您的问题询问文件夹是否包含文件或文件夹......但我认为大多数时候人们会认为如果目录不包含文件则为空.即如果它包含空子目录,它对我来说仍然是“空的”......这可能不适合您的使用,但可能适合其他人!

  public bool DirectoryIsEmpty(string path)
  
    int fileCount = Directory.GetFiles(path).Length;
    if (fileCount > 0)
    
        return false;
    

    string[] dirs = Directory.GetDirectories(path);
    foreach (string dir in dirs)
    
      if (! DirectoryIsEmpty(dir))
      
        return false;
      
    

    return true;
  

【讨论】:

Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories).Any()【参考方案9】:

简单易行:

public static bool DirIsEmpty(string path) 
    int num = Directory.GetFiles(path).Length + Directory.GetDirectories(path).Length;
    return num == 0;

【讨论】:

您还需要添加 GetDirectories 来计算它们!【参考方案10】:

在任何情况下,您都必须通过硬盘驱动器获取这些信息,仅此一项就胜过任何对象创建和数组填充。

【讨论】:

是的,尽管创建一些对象需要在磁盘上查找额外的元数据,这可能是不必要的。 每个对象肯定都需要 ACL。没有其他办法了。一旦您必须查找这些,您不妨阅读 MFT 标头中的任何其他信息以了解文件夹中的文件。【参考方案11】:

我不知道有一种方法可以简洁地告诉您给定文件夹是否包含任何其他文件夹或文件,但是,使用:

Directory.GetFiles(path);
&
Directory.GetDirectories(path);

应该有助于提高性能,因为这两种方法都只会返回带有文件/目录名称的字符串数组,而不是整个 FileSystemInfo 对象。

【讨论】:

【参考方案12】:

谢谢大家的回复。我尝试使用 Directory.GetFiles()Directory.GetDirectories() 方法。好消息!性能提高了~两倍! 229 次呼叫在 221 毫秒内。但我也希望,可以避免枚举文件夹中的所有项目。同意,仍然在执行不必要的工作。你不这么认为吗?

经过所有调查,我得出一个结论,在纯 .NET 下进一步优化是不可能的。我将使用 WinAPI 的 FindFirstFile 函数。希望它会有所帮助。

【讨论】:

出于兴趣,您需要如此高性能来执行此操作的原因是什么? 与其回答您自己的问题,不如将其中一个正确答案标记为答案(可能是第一个发布的答案或最清晰的答案)。这样,未来的 *** 用户将在您的问题下看到最佳答案!【参考方案13】:

有时您可能想验证子目录中是否存在任何文件并忽略那些空的子目录;在这种情况下,您可以使用以下方法:

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

【讨论】:

【参考方案14】:

基于 Brad Parks 代码:

    public static bool DirectoryIsEmpty(string path)
    
        if (System.IO.Directory.GetFiles(path).Length > 0) return false;

        foreach (string dir in System.IO.Directory.GetDirectories(path))
            if (!DirectoryIsEmpty(dir)) return false;

        return true;
    

【讨论】:

【参考方案15】:

由于您无论如何都要使用 DirectoryInfo 对象,所以我会使用扩展名

public static bool IsEmpty(this DirectoryInfo directoryInfo)

    return directoryInfo.GetFileSystemInfos().Count() == 0;

【讨论】:

【参考方案16】:

我的代码太棒了,它只花了 00:00:00.0007143 少于毫秒,文件夹中有 34 个文件

   System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
    sw.Start();

     bool IsEmptyDirectory = (Directory.GetFiles("d:\\pdf").Length == 0);

     sw.Stop();
     Console.WriteLine(sw.Elapsed);

【讨论】:

其实,如果你把它乘以 229 并加上 GetDirectories(),你会得到和我一样的结果:)【参考方案17】:

这里有一些东西可能会帮助你做到这一点。我设法在两次迭代中做到了。

 private static IEnumerable<string> GetAllNonEmptyDirectories(string path)
   
     var directories =
        Directory.EnumerateDirectories(path, "*.*", SearchOption.AllDirectories)
        .ToList();

     var directoryList = 
     (from directory in directories
     let isEmpty = Directory.GetFiles(directory, "*.*", SearchOption.AllDirectories).Length == 0
     where !isEmpty select directory)
     .ToList();

     return directoryList.ToList();
   

【讨论】:

【参考方案18】:

使用这个。很简单。

Public Function IsDirectoryEmpty(ByVal strDirectoryPath As String) As Boolean
        Dim s() As String = _
            Directory.GetFiles(strDirectoryPath)
        If s.Length = 0 Then
            Return True
        Else
            Return False
        End If
    End Function

【讨论】:

也许很简单。但不正确。它有两个主要错误:它不检测路径中是否有任何文件夹,只检测文件,并且它会在不存在的路径上抛出异常。它实际上也可能比 OP 的原始版本,因为我相当确定它会获取所有条目并过滤它们。

以上是关于如何快速检查文件夹是不是为空(.NET)?的主要内容,如果未能解决你的问题,请参考以下文章

如何检查文件内容是不是为空?

如何检查字节是不是为空 vb.net

如何在 windows phone 8.1 中检查文件夹是不是为空

如何在 Bash 中检查文件是不是为空?

Node.js:如何在不上传文件列表的情况下检查文件夹是不是为空

使用批处理命令检查文件夹是不是为空?