Klasörün boş olup olmadığını hızlı bir şekilde nasıl kontrol edebilirim (.NET)?


140

Diskteki dizinin boş olup olmadığını kontrol etmeliyim. Bu, herhangi bir klasör / dosya içermediği anlamına gelir. Biliyorum, basit bir yöntem var. FileSystemInfo dizisini alıyoruz ve eleman sayısının sıfıra eşit olup olmadığını kontrol ediyoruz. Bunun gibi bir şey:

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

Bu yaklaşım iyi görünüyor. FAKAT!! Performans açısından çok ama çok kötü. GetFileSystemInfos () çok zor bir yöntemdir. Aslında, klasörün tüm dosya sistemi nesnelerini numaralandırır, tüm özelliklerini alır, nesneleri oluşturur, yazılan diziyi doldurur vb. Ve tüm bunlar sadece uzunluğu kontrol etmek için. Bu aptalca, değil mi?

Ben sadece böyle bir kod profilli ve bu yöntem ~ 250 çağrı ~ 500ms yürütüldüğünü belirledim. Bu çok yavaş ve inanıyorum ki, çok daha hızlı yapmak mümkün.

Baska öneri?


7
Meraktan, dizini neden 250 kez kontrol etmek istersiniz?
ya23

2
@ ya23 Sanırım 250 farklı dizin kontrol etmek istiyorum. 250 kere tek bir tane bile değil.
Mathieu Pagé

Yanıtlar:


282

.NET 4'te Directoryve DirectoryInfoiçinde IEnumerable, bir dizi yerine dönmelerine ve tüm dizin içeriğini okumadan önce sonuçları döndürmelerine izin veren yeni bir özellik vardır .

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

EDIT: bu cevabı tekrar görerek, bu kodun çok daha basit olabileceğini anlıyorum ...

public bool IsDirectoryEmpty(string path)
{
    return !Directory.EnumerateFileSystemEntries(path).Any();
}

Bu çözümü beğendim, sadece belirli dosya türlerini kontrol etmek için yapılabilir mi? .Any () yerine değil ("jpg") çalışıyor gibi görünmüyor
Dennis

5
@Dennis, çağrısında bir joker karakter deseni belirtebilir EnumerateFileSystemEntriesveya kullanabilirsiniz .Any(condition)(koşulu lambda ifadesi veya parametre olarak yol alan bir yöntem olarak belirtebilirsiniz).
Thomas Levesque

Yazım hatası ilk kod örneğinden kaldırılabilir:return !items.GetEnumerator().MoveNext();
gary

1
@gary, bunu yaparsanız, numaralandırıcı imha edilmez, böylece numaralandırıcı çöp toplanana kadar dizini kilitler.
Thomas Levesque

Bu, Dosya içeren Dizinler için iyi çalışıyor gibi görünüyor, ancak Dizin başka Yöneticiler içeriyorsa, boş olduğunu söyleyerek geri gelir.
Kairan

32

İşte sonunda uyguladığım ekstra hızlı çözüm. Burada WinAPI ve FindFirstFile , FindNextFile işlevlerini kullanıyorum . Klasördeki tüm öğelerin numaralandırılmasını engeller ve Klasördeki ilk nesneyi algıladıktan hemen sonra durur . Bu yaklaşım yukarıda tarif edilenden ~ 6 (!!) kat daha hızlıdır. 36ms'de 250 çağrı!

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

Umarım gelecekte birileri için faydalı olur.


Çözümünüzü paylaştığınız için teşekkür ederiz.
Greg

3
Sen eklemeniz gerekir SetLastError = trueetmek DllImportiçin FindFirstFilesırayla Marshal.GetHRForLastWin32Error()arasında Açıklamalar bölümünde açıklandığı gibi, doğru işe çağrısı ) (GetHRForLastWin32Error için MSDN doc .
Joel V. Earnest-DeYoung

Alt dizinlerdeki dosyaları da aradığından cevabı takip etmek biraz daha iyi olduğunu düşünüyorum stackoverflow.com/questions/724148/…
Mayank

21

Sen-ebilmek denemek Directory.Exists(path)ve Directory.GetFiles(path)- muhtemelen daha az yükü (hiçbir nesne - sadece dizeleri vb).


Her zaman olduğu gibi, en hızlı tetikten! Beni birkaç saniye döv! :-)
Cerebrus

İkiniz de benden daha hızlıydınız ... dikkatimi detaylara lanet ediyorum ;-)
Eoin Campbell

2
Bana hiç iyi gelmedi; ilk cevap ve oy vermeyen tek kişi ;-(
Marc Gravell

Unfixed ... birisi öğütmek için bir balta var, methinks
Marc Gravell

1
GetFiles'in bir Dizin listesi alacağını sanmıyorum, bu yüzden GetDirectories için de bir kontrol koymak iyi bir fikir gibi görünüyor
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);
}

Bu hızlı test, boş olduğunda ve alt klasörler ve dosyalar içerdiğinde klasör için 2 milisaniyede geri geldi (her birinde 5 dosya bulunan 5 klasör)


3
Dosyaların listesini almak zorunda kalmadan 'dirs' hemen boş değilse geri dönerek bunu iyileştirebilirsiniz.
samjudson

3
Evet, ama içinde binlerce dosya varsa ne olur?
Thomas Levesque

3
Ayrıca, konsola yazma süresini de ölçüyorsunuz, bu da ihmal edilemez.
ctusch

11

Bunu klasörler ve dosyalar için kullanıyorum (optimal olup olmadığını bilmiyorum)

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

8

Saf C # bırakıp WinApi çağrıları için sorun olmazsa , PathIsDirectoryEmpty () işlevini düşünmek isteyebilirsiniz . MSDN'ye göre, fonksiyon:

PszPath boş bir dizinse TRUE değerini döndürür. PszPath bir dizin değilse veya "." Dışında en az bir dosya içeriyorsa FALSE değerini döndürür. veya "..".

Bu tam olarak ne istediğinizi yapan bir işlev gibi görünüyor, bu yüzden muhtemelen bu görev için iyi optimize edilmiş (bunu test etmeme rağmen).

C # 'dan aramak için pinvoke.net sitesi size yardımcı olmalıdır. (Ne yazık ki, henüz bu belirli işlevi tanımlamıyor, ancak benzer argümanlara sahip bazı işlevleri bulabilmeli ve orada geri dönebilmeli ve bunları çağrınızın temeli olarak kullanabilmelisiniz. Tekrar MSDN'ye bakarsanız, diyor ki alınacak DLL is shlwapi.dll)


İyi fikir. Bu işlevi bilmiyordum. Performansını yukarıda tarif ettiğim yaklaşımımla karşılaştırmaya çalışacağım. Daha hızlı yapardı, ben benim kodunda yeniden kullanırsınız. Teşekkürler.
zhe

4
Bu rotaya gitmek isteyenler için bir not. Shlwapi.dll dosyasının bu PathIsDirectoryEmpty () yöntemi Vista32 / 64 ve XP32 / 64 makinelerinde iyi çalışıyor, ancak bazı Win7 makinelerinde bombalanıyor. Bu, Windows'un farklı sürümleriyle birlikte gönderilen shlwapi.dll sürümleriyle ilgili bir şey olmalıdır. Dikkat.
Alex_P

7

Bu konuda performans istatistikleri bilmiyorum, ama Directory.GetFiles()statik yöntemi kullanmayı denediniz mi?

Dosya adlarını (FileInfos değil) içeren bir dize dizisi döndürür ve dizinin uzunluğunu yukarıdaki gibi kontrol edebilirsiniz.


aynı sorun, çok sayıda dosya varsa yavaş olabilir ... ama muhtemelen GetFileSystemInfos
Thomas Levesque

4

Eminim diğer yanıtlar daha hızlıdır ve sorunuz bir klasörün dosya veya klasör içerip içermediğini sordu ... ancak çoğu zaman insanların dosya içermiyorsa bir dizini boş olarak değerlendireceğini düşünürüm. yani boş alt dizinler içeriyorsa hala "boş" ... bu sizin kullanımınıza uygun olmayabilir, ama diğerleri için uygun olabilir!

  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()
Jonathan Gilbert

3

Her durumda bu bilgi için sabit sürücüye gitmeniz gerekecek ve bu tek başına herhangi bir nesne oluşturma ve dizi doldurmayı koyacaktır.


1
Doğru, bazı nesnelerin oluşturulması, diskte gerekli olmayabilecek ekstra meta veriler aramayı gerektirse de.
Adam Rosenfield

ACL her nesne için kesinlikle gerekli olacaktır. Etrafında bir yol yok. Bunları aramak zorunda kaldığınızda, klasördeki dosyalar için MFT başlıklarındaki diğer bilgileri de okuyabilirsiniz.
Don Reba

3

Belirli bir klasörün diğer klasörleri veya dosyaları içerip içermediğini, ancak aşağıdakileri kullanarak kısa bir süre içinde anlatacak bir yöntemin farkında değilim:

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

bu yöntemlerin her ikisi de tüm FileSystemInfo nesneleri yerine yalnızca dosyaların / dizinlerin adlarını içeren bir dizgi dizisi döndüreceğinden performansa yardımcı olmalıdır.


3

Kolay ve basit:

int num = Directory.GetFiles(pathName).Length;

if (num == 0)
{
   //empty
}

2

Cevaplar için herkese teşekkürler. Directory.GetFiles () ve Directory.GetDirectories () yöntemlerini kullanmaya çalıştım . İyi haberler! Performans ~ iki kez arttı! 221 ms'de 229 çağrı. Ama aynı zamanda, klasördeki tüm öğelerin numaralandırılmasından kaçınmanın mümkün olduğunu umuyorum. Katılıyorum, yine de gereksiz iş yürütüyor. Sen öyle düşünmüyor musun?

Tüm araştırmalardan sonra, saf .NET altında daha fazla optimizasyonun imkansız olduğu sonucuna vardım. WinAPI'nin FindFirstFile işlevi ile oynayacağım . Umarım yardımcı olur.


1
Bu operasyon için bu kadar yüksek performansa ihtiyaç duymanızın nedenleri nelerdir?
meandmycode

1
Kendi sorunuzu cevaplamak yerine, doğru cevaplardan birini cevap olarak işaretleyin (muhtemelen ilk ya da en net olanı). Bu şekilde, stackoverflow'un gelecekteki kullanıcıları sorunuzun altında en iyi yanıtı görecek!
Ray Hayes

2

Bir süre alt dizinlerde herhangi bir dosya olup olmadığını doğrulamak ve bu boş alt dizinleri yok saymak isteyebilirsiniz; bu durumda aşağıdaki yöntemi kullanabilirsiniz:

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

0

Merkezli Brad Parkı kodu:

    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

Kodum inanılmaz klasörde 34 dosya ile milisaniyeden daha az 00: 00: 00.0007143 aldı

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

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

     sw.Stop();
     Console.WriteLine(sw.Elapsed);

Aslında, 229 ile çarpıp GetDirectories () eklerseniz, benimkiyle aynı sonucu alırsınız :)
zhe

-1

İşte bunu yapmanıza yardımcı olabilecek bir şey. Bunu iki iterasyonda yapmayı başardım.

 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

Yine de bir DirectoryInfo nesnesiyle çalışacağınız için, bir uzantıyla giderim

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

-2

Bunu kullan. Basit.

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
Basit, belki. Ama yanlış. İki büyük hata vardır: Yolda herhangi bir klasör olup olmadığını algılamaz , yalnızca dosyalar ve varolmayan bir yola bir istisna atar. Ayrıca OP'nin orijinalinden daha yavaş olması muhtemeldir , çünkü tüm girişleri aldığından ve filtrelediğinden oldukça eminim.
Andrew Barber
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.