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


140

我必须检查磁盘上的目录是否为空。这意味着它不包含任何文件夹/文件。我知道,有一个简单的方法。我们获取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()是一个非常困难的方法。实际上,它枚举了文件夹的所有文件系统对象,获取了它们的所有属性,创建了对象,填充了类型化数组等。所有这些只是为了简单地检查Length。那是愚蠢的,不是吗?

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

有什么建议?


7
出于好奇,您为什么要检查目录250次?
09年

2
@ ya23我想一个人要检查250个差异目录。没有一个250倍。
马修页

Answers:


282

.NET 4 中DirectoryDirectoryInfo.NET 4中有一项新功能,使他们可以返回IEnumerable而不是数组,并在读取所有目录内容之前开始返回结果。

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

5
@Dennis,您可以在对的调用中指定通配符模式,也可以EnumerateFileSystemEntries使用.Any(condition)(将条件指定为lambda表达式,或者指定为以路径为参数的方法)。
Thomas Levesque 2013年

可以从第一个代码示例中删除类型转换:return !items.GetEnumerator().MoveNext();
gary 2014年

1
@gary,如果这样做,则不会处理枚举器,因此它将锁定目录,直到对枚举器进行垃圾回收为止。
Thomas Levesque 2014年

对于包含文件的目录,这似乎工作正常,但是如果目录包含其他目录,则会回来表示该目录为空。
凯兰2015年

32

这是我最终实现的超快速解决方案。在这里,我正在使用WinAPI和功能FindFirstFileFindNextFile。它可以避免枚举Folder中的所有项目,并在检测到Folder中的第一个对象后立即停止。这种方法比上述方法快约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();
}

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


感谢您分享您的解决方案。
格雷格2012年

3
您需要添加SetLastError = trueDllImportfor FindFirstFile中,以使Marshal.GetHRForLastWin32Error()调用正常工作,如GetHRForLastWin32Error()MSDN文档的“备注”部分所述。
Joel V. Earnest-DeYoung

我认为以下答案稍好一点,因为它还会在子目录stackoverflow.com/questions/724148/中
Mayank,

21

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


与往常一样,您最快可以触发!在那里击败我几秒钟!:-)
Cerebrus

你们俩都比我快...该死的我对细节的关注;-)
Eoin Campbell

2
但是对我没有任何好处。第一个答案,并且是唯一一个没有表决的答案;-(
马克·格雷夫

未解决...有人用斧头打磨,methinks
Marc Gravell

1
我不认为GetFiles会获得目录列表,因此最好同时检查一下GetDirectories
Kairan,2015年

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

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


3
您可以通过立即返回“ dirs”是否为非空来改进此方法,而不必获取文件列表。
samjudson,2009年

3
是的,但是如果其中有成千上万个文件怎么办?
Thomas Levesque

3
您还正在测量写入控制台的时间,这是可以忽略的。
ctusch 2013年

11

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

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

8

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

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

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

要从C#调用它,pinvoke.net网站应该会为您提供帮助。(不幸的是,它还没有描述这个特定的函数,但是您应该能够找到一些带有类似参数的函数,然后在其中返回类型,并将它们用作调用的基础。如果再次查看MSDN,它表示要导入的DLL是shlwapi.dll


好想法。我不知道这个功能。我将尝试将其性能与上述方法进行比较。如果可以更快执行,我将在代码中重用它。谢谢。

4
给那些想走这条路的人的说明。看来,来自shlwapi.dll的PathIsDirectoryEmpty()方法在Vista32 / 64和XP32 / 64机器上可以正常工作,但在某些Win7机器上却能正常工作。这一定与Windows的不同版本附带的shlwapi.dll版本有关。谨防。
Alex_P

7

我不了解这一项的性能统计信息,但是您是否尝试过使用 Directory.GetFiles()静态方法?

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


同样的问题,如果有许多文件,它可能会很慢...但它可能更快,这GetFileSystemInfos
托马斯Levesque的

4

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

  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()
乔纳森·吉尔伯特

3

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


1
是的,尽管创建一些对象涉及在磁盘上查找不必要的额外元数据。
亚当·罗森菲尔德2009年

确保每个对象都需要ACL。没有其他办法了。并且,一旦必须查找这些文件,您还可以阅读MFT标题中的该文件夹中文件的其他信息。
唐·雷巴

3

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

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

由于这两种方法都只会返回带有文件/目录名称的字符串数组,而不是整个FileSystemInfo对象的字符串数组。


2

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

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


1
出于兴趣,此操作需要如此高性能的原因是什么?
meandmycode

1
不要回答您自己的问题,而是将正确答案中的一个标记为答案(可能是第一个已发布的答案或最清晰的答案)。这样,将来stackoverflow的用户将在您的问题下看到最佳答案!
Ray Hayes

2

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

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


0

基于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;
    }

-1

我的代码很神奇,它 比文件夹中的34个文件花了不到毫秒的时间00:00:00.0007143

   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(),将得到与我的结果相同的结果:)
zhe

-1

这可能会对您有所帮助。我设法做了两次迭代。

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

-1

由于您仍然要使用DirectoryInfo对象,因此我将使用扩展名

public static bool IsEmpty(this DirectoryInfo directoryInfo)
{
    return directoryInfo.GetFileSystemInfos().Count() == 0;
}

-2

用这个。这很简单。

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

2
简单,也许。但是不正确。它有两个主要错误:它不会检测路径中是否有任何文件夹,仅检测文件,并且会在不存在的路径上引发异常。实际上,它也可能比OP的原始版本,因为我相当确定它会获取所有条目并对其进行过滤。
Andrew Barber 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.