폴더가 비어 있는지 빠르게 확인하는 방법 (.NET)?


140

디스크의 디렉토리가 비어 있는지 확인해야합니다. 이는 폴더 / 파일이 포함되어 있지 않음을 의미합니다. 나는 간단한 방법이 있다는 것을 알고 있습니다. FileSystemInfo의 배열을 가져 와서 요소 개수가 0인지 확인합니다. 그런 것 :

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 () 는 매우 어려운 방법입니다. 실제로 폴더의 모든 파일 시스템 객체를 열거하고 모든 속성을 가져오고 객체를 만들고 유형이 지정된 배열을 채 웁니다.이 모든 것은 단순히 길이를 확인하는 것입니다. 어리석지 않습니까?

방금 그러한 코드를 프로파일 링하고 ~ 250ms의 메소드 호출이 ~ 500ms에서 실행된다고 결정했습니다. 이것은 매우 느리며 훨씬 빨리 할 수 ​​있다고 믿습니다.

어떤 제안?


7
호기심 때문에 왜 디렉토리를 250 번 확인하고 싶습니까?
ya23

2
@ ya23 250 개의 differents 디렉토리를 확인하고 싶다고 생각합니다. 단 한 번도 250 번이 아닙니다.
Mathieu Pagé

답변:


282

새로운 기능이 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();
}

이 솔루션이 마음에 들어 특정 파일 형식 만 검사하도록 할 수 있습니까? .any () 대신 .Contains ( "jpg")가 작동하지 않는 것 같습니다
Dennis

5
@Dennis에서는에 대한 호출에서 와일드 카드 패턴을 지정 EnumerateFileSystemEntries하거나 사용할 수 있습니다 .Any(condition)(조건을 람다 식으로 지정하거나 경로를 매개 변수로 사용하는 메소드로 지정).
Thomas Levesque

타입 캐스트는 첫 번째 코드 예제에서 제거 될 수 있습니다.return !items.GetEnumerator().MoveNext();
gary

1
@gary, 그렇게하면 열거자가 폐기되지 않으므로 열거자가 가비지 수집 될 때까지 디렉토리를 잠급니다.
토마스 레베

이것은 파일을 포함하는 디렉토리에 대해서는 잘 작동하는 것으로 보이지만 디렉토리에 다른 디렉터가 포함되어 있으면 비어 있다고 표시됩니다.
Kairan

32

여기에 마지막으로 구현 한 추가 빠른 솔루션이 있습니다. 여기에서는 WinAPI 및 FindFirstFile , FindNextFile 함수를 사용하고 있습니다 . Folder의 모든 항목을 열거하지 않고 Folder 의 첫 번째 객체를 감지 한 직후 중지합니다 . 이 방법은 위에서 설명한 것보다 ~ 6 (!!) 배 빠릅니다. 36ms에서 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 = true받는 사람 DllImport에 대한 FindFirstFile의 순서 Marshal.GetHRForLastWin32Error()의 설명 부분에 설명 된대로 제대로 작동하려면 호출 ) (GetHRForLastWin32Error에 대한 MSDN의 문서 .
Joel V. Earnest-DeYoung 2014 년


21

시도 Directory.Exists(path)하고 Directory.GetFiles(path)-아마도 오버 헤드가 적습니다 (객체가 아닌 문자열 만).


언제나처럼, 당신은 방아쇠에서 가장 빠릅니다! 거기 몇 초나 날 이길! :-)
Cerebrus

당신은 나보다 빠르다 ... 디테일에주의를 기울여 라 ;-)
Eoin Campbell

2
그래도 나에게 잘하지 않았다; 첫 번째 답변, 그리고 투표가없는 유일한 사람 ;-(
Marc Gravell

고정되지 않은 ... 누군가가 갈기 위해 도끼를 가지고, 메타 크
Marc Gravell

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

3
예, 그러나 수천 개의 파일이 있으면 어떻게됩니까?
토마스 레베 스크

3
또한 콘솔에 쓸 시간을 측정하고 있습니다. 무시할 수 없습니다.
ctusch

11

폴더와 파일에 이것을 사용합니다 (최적인지 모르겠습니다)

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

8

순수한 C #을 남기지 않고 WinApi 호출 을 수행하는 것이 마음에 들지 않으면 PathIsDirectoryEmpty () 함수 를 고려할 수 있습니다 . MSDN에 따르면 기능은 다음과 같습니다.

pszPath가 빈 디렉토리 인 경우 TRUE를 리턴합니다. pszPath가 디렉토리가 아니거나 "."이외의 파일이 하나 이상 포함 된 경우 FALSE를 리턴합니다. 또는 "..".

그것은 당신이 원하는 것을 정확하게 수행하는 함수 인 것 같습니다. 그래서 그것은 아마도 그 작업에 대해 잘 최적화되어 있습니다 (테스트하지는 않았지만).

C #에서 호출하려면 pinvoke.net 사이트가 도움이 될 것입니다. (안타깝게도이 함수는 아직 설명하지 않았지만 비슷한 인수를 가진 함수를 찾아서 리턴 타입을 호출의 기초로 사용할 수 있어야합니다. MSDN을 다시 살펴보면 다음과 같습니다. 가져올 DLL은 shlwapi.dll)입니다.


좋은 생각이야 이 기능에 대해 몰랐습니다. 위에서 설명한 접근 방식과 성능을 비교하려고합니다. 그것이 더 빨라지면 내 코드에서 재사용 할 것입니다. 감사.
zhe

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 배 향상되었습니다! 221ms에 229의 전화. 그러나 폴더의 모든 항목이 열거되는 것을 피할 수 있기를 바랍니다. 여전히 불필요한 작업이 실행되고 있음에 동의하십시오. 그렇게 생각하지 않습니까?

모든 조사를 마친 후에 순수한 .NET에서는 추가 최적화가 불가능하다는 결론에 도달했습니다. WinAPI의 FindFirstFile 함수 를 사용하겠습니다 . 그것이 도움이되기를 바랍니다.


1
관심이 없다면,이 작업에 고성능이 필요한 이유는 무엇입니까?
meandmycode 2009

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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.