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

发布于 2024-07-16 13:56:35 字数 723 浏览 3 评论 0原文

我必须检查磁盘上的目录是否为空。 这意味着它不包含任何文件夹/文件。 我知道,有一个简单的方法。 我们获取 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 毫秒内执行。 这是非常慢的,我相信,可以做得更快。

有什么建议么?

I have to check, if directory on disk is empty. It means, that it does not contain any folders/files. I know, that there is a simple method. We get array of FileSystemInfo's and check if count of elements equals to zero. Something like that:

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

This approach seems OK. BUT!! It is very, very bad from a perspective of performance. GetFileSystemInfos() is a very hard method. Actually, it enumerates all filesystem objects of folder, gets all their properties, creates objects, fills typed array etc. And all this just to simply check Length. That's stupid, isn't it?

I just profiled such code and determined, that ~250 calls of such method are executed in ~500ms. This is very slow and I believe, that it is possible to do it much quicker.

Any suggestions?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(18

愛放△進行李 2024-07-23 13:56:35

.NET 4 中的 DirectoryDirectoryInfo 中有一个新功能,允许它们返回 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();
}

There is a new feature in Directory and DirectoryInfo in .NET 4 that allows them to return an IEnumerable instead of an array, and start returning results before reading all the directory contents.

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

EDIT: seeing that answer again, I realize this code can be made much simpler...

public bool IsDirectoryEmpty(string path)
{
    return !Directory.EnumerateFileSystemEntries(path).Any();
}
桃扇骨 2024-07-23 13:56:35

这是我最终实现的超快速解决方案。 在这里,我使用 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();
}

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

Here is the extra fast solution, that I finally implemented. Here I am using WinAPI and functions FindFirstFile, FindNextFile. It allows to avoid enumeration of all items in Folder and stops right after detecting the first object in the Folder. This approach is ~6(!!) times faster, than described above. 250 calls in 36ms!

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

I hope it will be useful for somebody in the future.

分分钟 2024-07-23 13:56:35

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

You could try Directory.Exists(path) and Directory.GetFiles(path) - probably less overhead (no objects - just strings etc).

忘年祭陌 2024-07-23 13:56:35
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 个文件)

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

This quick test came back in 2 milliseconds for the folder when empty and when containing subfolders & files (5 folders with 5 files in each)

云裳 2024-07-23 13:56:35

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

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

I use this for folders and files (don't know if it's optimal)

    if(Directory.GetFileSystemEntries(path).Length == 0)
日裸衫吸 2024-07-23 13:56:35

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

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

这似乎是一个完全符合您想要的功能的函数,因此它可能针对该任务进行了很好的优化(尽管我还没有测试过)。

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

If you don't mind leaving pure C# and going for WinApi calls, then you might want to consider the PathIsDirectoryEmpty() function. According to the MSDN, the function:

Returns TRUE if pszPath is an empty directory. Returns FALSE if pszPath is not a directory, or if it contains at least one file other than "." or "..".

That seems to be a function which does exactly what you want, so it is probably well optimised for that task (although I haven't tested that).

To call it from C#, the pinvoke.net site should help you. (Unfortunately, it doesn't describe this certain function yet, but you should be able to find some functions with similar arguments and return type there and use them as the basis for your call. If you look again into the MSDN, it says that the DLL to import from is shlwapi.dll)

迷迭香的记忆 2024-07-23 13:56:35

我不知道这方面的性能统计数据,但是您是否尝试过使用 Directory.GetFiles() 静态方法?

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

I don't know about the performance statistics on this one, but have you tried using the Directory.GetFiles() static method ?

It returns a string array containing filenames (not FileInfos) and you can check the length of the array in the same way as above.

浅听莫相离 2024-07-23 13:56:35

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

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

I'm sure the other answers are faster, and your question asked for whether or not a folder contained files or folders... but I'd think most of the time people would consider a directory empty if it contains no files. ie It's still "empty" to me if it contains empty subdirectories... this may not fit for your usage, but may for others!

  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;
  }
日久见人心 2024-07-23 13:56:35

简单又简单:

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

Easy and simple:

public static bool DirIsEmpty(string path) {
    int num = Directory.GetFiles(path).Length + Directory.GetDirectories(path).Length;
    return num == 0;
}
无畏 2024-07-23 13:56:35

无论如何,您都必须从硬盘驱动器中获取此信息,仅此一项就胜过任何对象创建和数组填充。

You will have to go the hard drive for this information in any case, and this alone will trump any object creation and array filling.

聆听风音 2024-07-23 13:56:35

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

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

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

I'm not aware of a method that will succinctly tell you if a given folder contains any other folders or files, however, using:

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

should help performance since both of these methods will only return an array of strings with the names of the files/directories rather than entire FileSystemInfo objects.

静水深流 2024-07-23 13:56:35

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

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

Thanks, everybody, for replies. I tried to use Directory.GetFiles() and Directory.GetDirectories() methods. Good news! The performance improved ~twice! 229 calls in 221ms. But also I hope, that it is possible to avoid enumeration of all items in the folder. Agree, that still the unnecessary job is executing. Don't you think so?

After all investigations, I reached a conclusion, that under pure .NET further optimiation is impossible. I am going to play with WinAPI's FindFirstFile function. Hope it will help.

短暂陪伴 2024-07-23 13:56:35

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

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

Some time you might want to verify whether any files exist inside sub directories and ignore those empty sub directories; in this case you can used method below:

public bool isDirectoryContainFiles(string path) {
    if (!Directory.Exists(path)) return false;
    return Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories).Any();
}
顾北清歌寒 2024-07-23 13:56:35

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

Based in Brad Parks code:

    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;
    }
抱猫软卧 2024-07-23 13:56:35

我的代码太棒了,它只需要 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);

My code is amazing it just took 00:00:00.0007143
less than milisecond with 34 file in folder

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

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

     sw.Stop();
     Console.WriteLine(sw.Elapsed);
写下不归期 2024-07-23 13:56:35

这里有一些东西可能会帮助你做到这一点。 我成功地通过两次迭代完成了它。

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

Here is something that might help you doing it. I managed to do it in two iterations.

 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();
   }
橘虞初梦 2024-07-23 13:56:35

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

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

Since you are going to work with a DirectoryInfo object anyway, I'd go with an extension

public static bool IsEmpty(this DirectoryInfo directoryInfo)
{
    return directoryInfo.GetFileSystemInfos().Count() == 0;
}
一袭白衣梦中忆 2024-07-23 13:56:35

用这个。 这很简单。

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

Use this. It's simple.

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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文