1. ホーム
  2. .net

[解決済み] フォルダが空かどうかをすばやく確認する方法(.NET)?

2022-04-26 04:24:50

質問

ディスク上のディレクトリが空であるかどうかを確認する必要があります。これは、フォルダやファイルが含まれていないことを意味します。私は、簡単な方法があることを知っています。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回の呼び出しが500msで実行されることがわかりました。これは非常に遅く、もっと速く実行することが可能だと思います。

何か提案はありますか?

解決方法は?

これは、私が最終的に実装した超高速ソリューションです。ここでは、WinAPIと関数を使っています。 FindFirstFile , FindNextFile . これにより、フォルダ内のすべてのアイテムの列挙を回避して フォルダ内の最初のオブジェクトを検出した直後に停止する。 . この方法は、上記の方法よりも6倍(!)高速です。250回の呼び出しで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();
}

将来、誰かの役に立つことを願っています。