フォルダーが空(.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()は非常に難しいメソッドです。実際には、それはフォルダーのすべてのファイルシステムオブジェクトを列挙し、それらのすべてのプロパティを取得し、オブジェクトを作成し、型付き配列に入力します。ばかだよね?

私はそのようなコードをプロファイルし、そのようなメソッドの〜250の呼び出しが〜500msで実行されることを決定しました。これは非常に遅く、私はそれをはるかに速く行うことが可能であると信じています。

助言がありますか?


7
好奇心から、ディレクトリを250回確認したいのはなぜですか。
ya23 2009

2
@ ya23 250の異なるディレクトリをチェックしたいと思います。250回は1つではありません。
MathieuPagé2013

回答:


282

.NET 4 DirectoryおよびDirectoryInfo.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();
}

私はこのソリューションが好きですが、特定のファイルタイプのみをチェックするようにできますか?.any()の代わりに.Contains( "jpg")が機能しないようでした
Dennis

5
@Dennisでは、への呼び出しEnumerateFileSystemEntriesまたは使用でワイルドカードパターンを指定できます.Any(condition)(条件をラムダ式として、またはパスをパラメーターとして取るメソッドとして指定)。
Thomas Levesque 2013

型キャストは、最初のコード例から除去することができる:return !items.GetEnumerator().MoveNext();
ゲイリー

1
@gary、これを行うと、列挙子は破棄されないため、列挙子がガベージコレクションされるまでディレクトリがロックされます。
Thomas Levesque 14

これはファイルを含むディレクトリでは問題なく機能するようですが、ディレクトリに他のディレクトリが含まれている場合、空であると表示されます。
カイラン

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

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


ソリューションを共有していただきありがとうございます。
グレッグ

3
あなたは追加する必要があるSetLastError = trueDllImportためにFindFirstFileためにMarshal.GetHRForLastWin32Error()の備考セクションで説明したように、正しく仕事への呼び出し)(GetHRForLastWin32ErrorためのMSDNドキュメント
Joel V. Earnest-DeYoung 2013年

私はそれがまた、サブディレクトリ内のファイルを探して、次の答えは少し良いと思いstackoverflow.com/questions/724148/...
Mayank

21

あなたは試すことができなかったDirectory.Exists(path)し、Directory.GetFiles(path)おそらく少ないオーバーヘッド(何のオブジェクト- -文字列だけなど)。


いつものように、あなたはトリガーの外で最速です!そこで数秒で私を倒してください!:-)
セレブラス2009

あなたはどちらも私よりも速かったです...細部への私の注意を酷評しました;-)
Eoin Campbell

2
しかし、私には何の役にも立ちませんでした。最初の回答、そして投票なしの唯一の回答
;-(

修正されていません...誰かが挽く斧を持っています、メティンク
マーク・グラベル

1
GetFilesがディレクトリのリストを取得するとは思わないので、GetDirectoriesもチェックすることをお勧めします
Kairan

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

このクイックテストは、フォルダーが空の場合、およびサブフォルダーとファイル(5つのフォルダーにそれぞれ5つのファイルが含まれる)が含まれている場合、2ミリ秒で戻りました。


3
「dirs」がすぐに空でない場合は、ファイルのリストを取得せずに戻ることでこれを改善できます。
samjudson 2009年

3
はい、でも何千ものファイルがある場合はどうなりますか?
トーマスレベスク

3
また、コンソールに書き込む時間も測定していますが、これは無視できません。
ctusch 2013

11

私はこれをフォルダーとファイルに使用します(最適かどうかわかりません)

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

8

純粋なC#を離れてWinApi呼び出しを行うことを気にしない場合は、PathIsDirectoryEmpty()関数を検討することをお勧めします。MSDNによると、関数:

pszPathが空のディレクトリの場合、TRUEを返します。pszPathがディレクトリでない場合、または「。」以外のファイルが少なくとも1つ含まれている場合は、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()静的メソッドを使用してみましたか?

ファイル名(FileInfoではない)を含む文字列配列を返し、上記と同じ方法で配列の長さを確認できます。


同じ問題、多くのファイルがある場合は遅くなる可能性があります...しかし、GetFileSystemInfosよりも高速である可能性があります
Thomas 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
確かに、一部のオブジェクトの作成には、ディスク上で不要な可能性がある追加のメタデータの検索が含まれます。
Adam Rosenfield、

確かに、ACLはすべてのオブジェクトに必要です。それを回避する方法はありません。そして、それらを検索する必要がある場合は、フォルダー内のファイルのMFTヘッダーにある他の情報も読む必要があります。
Don Reba

3

特定のフォルダーに他のフォルダーまたはファイルが含まれているかどうかを簡潔に通知する方法は知りませんが、次の方法を使用します。

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

これらのメソッドは両方とも、FileSystemInfoオブジェクト全体ではなく、ファイル/ディレクトリの名前を含む文字列の配列のみを返すため、パフォーマンスに役立つはずです。


2

返信ありがとうございます。Directory.GetFiles()Directory.GetDirectories()を使用しようとしましたメソッド。朗報!パフォーマンスが2倍に向上しました!229ミリ秒で229コール。しかし、私はまた、フォルダ内のすべてのアイテムの列挙を回避できることを願っています。まだ、不要なジョブが実行されていることに同意します。そう思いませんか?

すべての調査の結果、純粋な.NETではそれ以上の最適化は不可能であるという結論に達しました。WinAPIのFindFirstFile関数を操作します。お役に立てば幸いです。


1
興味深いことに、この操作にこのような高いパフォーマンスが必要な理由は何ですか?
meandmycode 2009

1
あなた自身の質問に答えるのではなく、正しい答えの1つを答えとしてマークしてください(おそらく最初に投稿されたものか、最も明確なもの)。このようにして、stackoverflowの将来のユーザーは、あなたの質問のすぐ下に最良の答えが表示されます!
レイ・ヘイズ

2

サブディレクトリ内にファイルが存在するかどうかを確認し、それらの空のサブディレクトリを無視したい場合があります。この場合、以下の方法を使用できます。

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


0

ブラッドパークスコードに基づく:

    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

ここにそれを行うのを助けるかもしれない何かがあります。私は何とかそれを2回繰り返しました。

 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
おそらく、単純です。しかし不正解です。これには2つの大きなバグがあります。パスにフォルダが含まれているかどうかを検出せず、ファイルのみを検出します。存在しないパスで例外をスローします。また、すべてのエントリを取得してフィルタリングするので、OPのオリジナルよりも実際には遅くなる可能性があります。
Andrew Barber
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.