Bagaimana cara cepat memeriksa apakah folder kosong (.NET)?

140

Saya harus memeriksa, apakah direktori pada disk kosong. Artinya, tidak mengandung folder / file apa pun. Saya tahu, bahwa ada metode sederhana. Kami mendapatkan larik FileSystemInfo dan memeriksa apakah jumlah elemen sama dengan nol. Sesuatu seperti itu:

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

Pendekatan ini tampaknya baik-baik saja. TAPI!! Ini sangat, sangat buruk dari perspektif kinerja. GetFileSystemInfos () adalah metode yang sangat sulit. Sebenarnya, ia menghitung semua objek filesystem folder, mendapatkan semua propertinya, membuat objek, mengisi array yang diketik, dll. Dan semua ini hanya dengan cukup memeriksa Panjang. Itu bodoh, bukan?

Saya baru saja membuat profil kode seperti itu dan memutuskan, bahwa ~ 250 panggilan metode seperti itu dieksekusi dalam ~ 500ms. Ini sangat lambat dan saya percaya, adalah mungkin untuk melakukannya lebih cepat.

Ada saran?

zhe
sumber
7
Karena penasaran, mengapa Anda ingin memeriksa direktori 250 kali?
ya23
2
@ ya23 Saya kira orang ingin memeriksa 250 direktori differents. Tidak satu pun 250 kali.
Mathieu Pagé

Jawaban:

282

Ada fitur baru di dalam Directorydan DirectoryInfo.NET 4 yang memungkinkan mereka untuk mengembalikan IEnumerablebukan array, dan mulai mengembalikan hasil sebelum membaca semua konten direktori.

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

EDIT: melihat jawaban itu lagi, saya menyadari kode ini dapat dibuat lebih sederhana ...

public bool IsDirectoryEmpty(string path)
{
    return !Directory.EnumerateFileSystemEntries(path).Any();
}
Thomas Levesque
sumber
Saya suka solusi ini, dapatkah dibuat hanya untuk memeriksa tipe file tertentu? .Contains ("jpg") alih-alih .any () tampaknya tidak berfungsi
Dennis
5
@ Dennis, Anda dapat menentukan pola wildcard dalam panggilan untuk EnumerateFileSystemEntries, atau menggunakan .Any(condition)(tentukan kondisi sebagai ekspresi lambda, atau sebagai metode yang mengambil jalur sebagai parameter).
Thomas Levesque
Typecast dapat dihapus dari contoh kode pertama:return !items.GetEnumerator().MoveNext();
gary
1
@gary, jika Anda melakukan itu, enumerator tidak akan dibuang, jadi ia akan mengunci direktori sampai enumerator dikumpulkan menjadi sampah.
Thomas Levesque
Tampaknya ini berfungsi dengan baik untuk Direktori yang berisi File, tetapi jika Direktori berisi Direktur lain, itu kembali mengatakan itu kosong.
Kairan
32

Inilah solusi ekstra cepat, yang akhirnya saya terapkan. Di sini saya menggunakan WinAPI dan fungsi FindFirstFile , FindNextFile . Hal ini memungkinkan untuk menghindari penghitungan semua item di Folder dan berhenti tepat setelah mendeteksi objek pertama di Folder . Pendekatan ini ~ 6 (!!) kali lebih cepat, daripada yang dijelaskan di atas. 250 panggilan dalam 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();
}

Saya berharap ini akan bermanfaat bagi seseorang di masa depan.

zhe
sumber
Terima kasih telah membagikan solusi Anda.
Greg
3
Anda perlu menambahkan SetLastError = trueke DllImportuntuk FindFirstFileagar Marshal.GetHRForLastWin32Error()panggilan berfungsi dengan benar, seperti yang dijelaskan di bagian Keterangan pada dokumen MSDN untuk GetHRForLastWin32Error () .
Joel V. Earnest-DeYoung
Saya pikir jawaban berikut sedikit lebih baik karena juga mencari file di sub direktori stackoverflow.com/questions/724148/…
Mayank
21

Anda dapat mencoba Directory.Exists(path)dan Directory.GetFiles(path)- mungkin lebih sedikit overhead (tidak ada objek - hanya string dll).

Marc Gravell
sumber
Seperti biasa, Anda tercepat dari pemicunya! Kalahkan saya beberapa detik di sana! :-)
Cerebrus
Anda berdua lebih cepat dari saya ... sialan perhatian saya pada detail ;-)
Eoin Campbell
2
Namun, tidak ada gunanya bagiku; jawaban pertama, dan satu-satunya tanpa suara ;-(
Marc Gravell
Tidak diperbaiki ... seseorang memiliki kapak untuk digiling, methinks
Marc Gravell
1
Saya tidak berpikir GetFiles akan mendapatkan daftar Direktori, jadi sepertinya ide yang baik untuk memberikan cek untuk
GetDirectories
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);
}

Tes cepat ini kembali dalam 2 milidetik untuk folder saat kosong dan ketika berisi subfolder & file (masing-masing 5 folder dengan 5 file)

Eoin Campbell
sumber
3
Anda dapat meningkatkan ini dengan mengembalikan jika 'dir' tidak langsung kosong, tanpa harus mendapatkan daftar file.
samjudson
3
Ya, tetapi bagaimana jika ada ribuan file di dalamnya?
Thomas Levesque
3
Anda juga mengukur waktu untuk menulis ke konsol, yang tidak dapat diabaikan.
ctusch
11

Saya menggunakan ini untuk folder dan file (tidak tahu apakah itu optimal)

    if(Directory.GetFileSystemEntries(path).Length == 0)
Jmu
sumber
8

Jika Anda tidak keberatan meninggalkan C # murni dan pergi untuk panggilan WinApi , maka Anda mungkin ingin mempertimbangkan fungsi PathIsDirectoryEmpty () . Menurut MSDN, fungsinya:

Mengembalikan BENAR jika pszPath adalah direktori kosong. Mengembalikan FALSE jika pszPath bukan direktori, atau jika mengandung setidaknya satu file selain "." atau "..".

Tampaknya itu adalah fungsi yang melakukan apa yang Anda inginkan, jadi mungkin dioptimalkan dengan baik untuk tugas itu (walaupun saya belum mengujinya).

Untuk memanggilnya dari C #, situs pinvoke.net akan membantu Anda. (Sayangnya, ini belum menggambarkan fungsi tertentu ini, tetapi Anda harus dapat menemukan beberapa fungsi dengan argumen yang sama dan kembali ketik di sana dan menggunakannya sebagai dasar untuk panggilan Anda. Jika Anda melihat lagi ke dalam MSDN, dikatakan bahwa DLL untuk mengimpor dari is shlwapi.dll)

alias
sumber
Ide yang hebat. Saya tidak tahu tentang fungsi ini. Saya akan mencoba membandingkan kinerjanya dengan pendekatan saya, yang saya jelaskan di atas. Jika lebih cepat, saya akan menggunakannya kembali dalam kode saya. Terima kasih.
zhe
4
Catatan untuk mereka yang ingin menempuh rute ini. Tampaknya metode PathIsDirectoryEmpty () dari shlwapi.dll berfungsi dengan baik pada mesin Vista32 / 64 dan XP32 / 64, tetapi gagal pada beberapa mesin Win7. Pasti ada hubungannya dengan versi shlwapi.dll yang dikirimkan dengan berbagai versi Windows. Awas.
Alex_P
7

Saya tidak tahu tentang statistik kinerja yang satu ini, tetapi apakah Anda sudah mencoba menggunakan Directory.GetFiles()metode statis?

Ini mengembalikan array string yang berisi nama file (bukan FileInfos) dan Anda dapat memeriksa panjang array dengan cara yang sama seperti di atas.

Cerebrus
sumber
masalah yang sama, bisa lambat jika ada banyak file ... tapi mungkin lebih cepat yang GetFileSystemInfos
Thomas Levesque
4

Saya yakin jawaban lain lebih cepat, dan pertanyaan Anda menanyakan apakah folder berisi file atau folder ... tapi saya pikir sebagian besar waktu orang akan menganggap direktori kosong jika tidak mengandung file. yaitu masih "kosong" bagi saya jika berisi subdirektori kosong ... ini mungkin tidak cocok untuk penggunaan Anda, tetapi mungkin untuk orang lain!

  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;
  }
Taman Brad
sumber
Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories).Any()
Jonathan Gilbert
3

Anda harus pergi ke hard drive untuk informasi ini, dan ini saja akan mengalahkan pembuatan objek dan mengisi array.

Don Reba
sumber
1
Benar, meskipun membuat beberapa objek melibatkan mencari metadata tambahan pada disk yang mungkin tidak diperlukan.
Adam Rosenfield
ACL akan diperlukan untuk setiap objek pasti. Tidak ada jalan lain. Dan begitu Anda harus mencari itu, Anda mungkin juga membaca informasi lain di header MFT untuk file dalam folder.
Don Reba
3

Saya tidak mengetahui metode yang secara ringkas akan memberi tahu Anda jika folder yang diberikan berisi folder atau file lain, menggunakan:

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

seharusnya membantu kinerja karena kedua metode ini hanya akan mengembalikan array string dengan nama file / direktori daripada seluruh objek FileSystemInfo.

CraigTP
sumber
2

Terima kasih, semuanya, untuk balasan. Saya mencoba menggunakan metode Directory.GetFiles () dan Directory.GetDirectories () . Kabar baik! Kinerja meningkat ~ dua kali lipat! 229 panggilan dalam 221ms. Tetapi saya juga berharap, bahwa mungkin untuk menghindari penghitungan semua item dalam folder. Setuju, bahwa masih pekerjaan yang tidak perlu dijalankan. Bukankah begitu?

Setelah semua investigasi, saya mencapai kesimpulan, bahwa di bawah. NET optimisasi lebih lanjut tidak mungkin. Saya akan bermain dengan fungsi FindFirstFile WinAPI . Semoga ini bisa membantu.

zhe
sumber
1
Karena ketertarikan, apa alasan Anda membutuhkan kinerja tinggi untuk operasi ini?
meandmycode
1
Daripada menjawab pertanyaan Anda sendiri, tandai salah satu jawaban yang benar sebagai jawabannya (mungkin yang pertama diposting atau yang paling jelas). Dengan cara ini, pengguna stackoverflow di masa depan akan melihat jawaban terbaik tepat di bawah pertanyaan Anda!
Ray Hayes
2

Beberapa waktu Anda mungkin ingin memverifikasi apakah ada file di dalam sub direktori dan mengabaikan sub direktori kosong tersebut; dalam hal ini Anda dapat menggunakan metode di bawah ini:

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

Mudah dan sederhana:

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

if (num == 0)
{
   //empty
}
Matheus Miranda
sumber
0

Berbasis di kode 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;
    }
Ibngel Ibáñez
sumber
-1

Kode saya luar biasa hanya butuh 00: 00: 00.0007143 kurang dari milidetik dengan 34 file di 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);
Prashant Cholachagudda
sumber
Sebenarnya, jika Anda kalikan dengan 229 dan tambahkan GetDirectories (), Anda akan mendapatkan hasil yang sama, seperti milik saya :)
zhe
-1

Ini ada sesuatu yang mungkin bisa membantu Anda melakukannya. Saya berhasil melakukannya dalam dua iterasi.

 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();
   }
Gabriel Marius Popescu
sumber
-1

Karena Anda akan bekerja dengan objek DirectoryInfo, saya akan pergi dengan ekstensi

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

Gunakan ini. Itu mudah.

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
Puffgroovy
sumber
2
Sederhana, mungkin. Tapi salah. Ini memiliki dua bug utama: Ini tidak mendeteksi jika ada folder di jalur, hanya file, dan itu akan membuang pengecualian pada jalur yang tidak ada. Ini juga cenderung benar-benar lebih lambat daripada yang asli OP, karena saya cukup yakin itu mendapatkan semua entri dan menyaringnya.
Andrew Barber