จะตรวจสอบได้อย่างรวดเร็วว่าโฟลเดอร์ว่างเปล่าหรือไม่ (. 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

2
@ ya23 ฉันคิดว่าใครอยากจะตรวจสอบไดเรกทอรี 250 ไดเรกทอรี ไม่ใช่คนเดียว 250 ครั้ง
Mathieu Pagé

คำตอบ:


282

มีคุณสมบัติใหม่ใน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();
}

ฉันชอบโซลูชันนี้สามารถตรวจสอบเฉพาะไฟล์บางประเภทได้หรือไม่ .Contains ( "JPG") แทน .any () ไม่ได้ดูเหมือนจะทำงาน
เดนนิส

5
@Dennis คุณสามารถระบุรูปแบบไวด์การ์ดในการเรียกEnumerateFileSystemEntriesหรือใช้.Any(condition)(ระบุเงื่อนไขเป็นนิพจน์แลมบ์ดาหรือเป็นวิธีที่ใช้พา ธ เป็นพารามิเตอร์)
โทมัสเลเวส

typecast สามารถลบออกจากตัวอย่างรหัสแรก:return !items.GetEnumerator().MoveNext();
แกรี่

1
@ แกรี่หากคุณทำเช่นนั้นตัวแจงนับจะไม่ถูกกำจัดดังนั้นมันจะล็อกไดเรกทอรีจนกว่าตัวแจงนับจะถูกรวบรวมขยะ
โทมัสเลวีส

ดูเหมือนว่าจะทำงานได้ดีสำหรับไดเรกทอรีที่มีไฟล์ แต่ถ้าไดเรกทอรีมีผู้กำกับคนอื่น ๆ ก็จะกลับมาบอกว่ามันว่างเปล่า
Kairan

32

นี่เป็นวิธีแก้ปัญหาที่รวดเร็วเป็นพิเศษซึ่งในที่สุดฉันก็นำไปใช้ นี่ฉันใช้ WinAPI และฟังก์ชั่FindFirstFile , FindNextFile จะช่วยให้หลีกเลี่ยงการแจงนับของรายการทั้งหมดในโฟลเดอร์และหยุดทันทีหลังจากการตรวจสอบวัตถุแรกในโฟลเดอร์ วิธีนี้เร็วกว่า ~ 6 (!!) มากกว่าที่อธิบายไว้ข้างต้น 250 สายใน 36 มิลลิวินาที!

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()การเรียกร้องให้ทำงานได้อย่างถูกต้องตามที่อธิบายไว้ในส่วนข้อสังเกตของเอกสาร MSDN สำหรับ GetHRForLastWin32Error ()
Joel V. Earnest-DeYoung

ฉันคิดว่าคำตอบต่อไปนี้จะดีขึ้นเล็กน้อยเพราะมันจะค้นหาไฟล์ในไดเรกทอรีย่อยstackoverflow.com/questions/724148/ …
Mayank

21

คุณสามารถลองDirectory.Exists(path)และDirectory.GetFiles(path)- อาจจะน้อยกว่าค่าใช้จ่าย (ไม่มีวัตถุ - เพียงแค่สตริง ฯลฯ )


และเช่นเคยคุณเป็นไกที่เร็วที่สุด! เอาชนะฉันไม่กี่วินาทีที่นั่น! :-)
Cerebrus

คุณทั้งสองได้เร็วกว่าฉัน ... แช่งความสนใจของฉันในรายละเอียด ;-)
เอียนแคมป์เบล

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

การทดสอบอย่างรวดเร็วนี้กลับมาใน 2 มิลลิวินาทีสำหรับโฟลเดอร์เมื่อว่างเปล่าและเมื่อมีโฟลเดอร์ย่อยและไฟล์ (5 โฟลเดอร์ที่มี 5 ไฟล์ในแต่ละไฟล์)


3
คุณสามารถปรับปรุงสิ่งนี้ได้ด้วยการส่งคืนหาก 'dirs' ไม่ว่างเปล่าเลยโดยไม่ต้องรับรายการไฟล์
samjudson

3
ใช่ แต่จะเกิดอะไรขึ้นถ้ามีไฟล์หลายพันไฟล์?
โทมัส Levesque

3
คุณกำลังวัดเวลาในการเขียนไปยังคอนโซลซึ่งไม่ได้เล็กน้อย
ctusch

11

ฉันใช้สิ่งนี้สำหรับโฟลเดอร์และไฟล์ (ไม่ทราบว่าเหมาะสมหรือไม่)

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

8

หากคุณไม่ทราบออกบริสุทธิ์ C # และจะให้Winapiโทรแล้วคุณอาจต้องการที่จะต้องพิจารณาPathIsDirectoryEmpty ()ฟังก์ชั่น ตาม MSDN ฟังก์ชั่น:

ส่งคืน TRUE หาก pszPath เป็นไดเรกทอรีว่าง ส่งคืน FALSE หาก pszPath ไม่ใช่ไดเรกทอรีหรือหากมีไฟล์อย่างน้อยหนึ่งไฟล์ที่ไม่ใช่ "." หรือ "..".

ดูเหมือนว่าจะเป็นฟังก์ชั่นที่ทำในสิ่งที่คุณต้องการอย่างแน่นอนดังนั้นจึงเหมาะสำหรับงานนั้น (แม้ว่าฉันจะยังไม่ได้ทดสอบก็ตาม)

หากต้องการโทรจาก C # เว็บไซต์pinvoke.netจะช่วยคุณ (น่าเสียดายที่มันยังไม่ได้อธิบายฟังก์ชั่นบางอย่างนี้ แต่คุณควรจะสามารถหาฟังก์ชั่นบางอย่างที่มีข้อโต้แย้งที่คล้ายกันและกลับมาพิมพ์ที่นั่นและใช้พวกมันเป็นพื้นฐานสำหรับการโทรของคุณ DLL ที่จะนำเข้าจากคือshlwapi.dll)


ความคิดที่ดี. ฉันไม่รู้เกี่ยวกับฟังก์ชั่นนี้ ฉันจะพยายามเปรียบเทียบประสิทธิภาพกับแนวทางที่ฉันอธิบายไว้ข้างต้น หากทำได้เร็วกว่านี้ฉันจะใช้ซ้ำในรหัสของฉัน ขอบคุณ
Zhe

4
หมายเหตุสำหรับผู้ที่ต้องการไปเส้นทางนี้ ดูเหมือนว่าวิธี PathIsDirectoryEmpty () นี้จาก shlwapi.dll ทำงานได้ดีบนเครื่อง Vista32 / 64 และ XP32 / 64 แต่ระเบิดออกมาในเครื่อง Win7 บางเครื่อง ต้องเป็นสิ่งที่ต้องทำกับรุ่นของ shlwapi.dll ที่ส่งมาพร้อมกับ Windows รุ่นต่างๆ ระวัง.
Alex_P

7

ฉันไม่รู้เกี่ยวกับสถิติประสิทธิภาพการทำงานของอันนี้ แต่คุณลองใช้Directory.GetFiles()วิธี static หรือไม่?

มันส่งกลับอาร์เรย์สตริงที่มีชื่อไฟล์ (ไม่ใช่ FileInfos) และคุณสามารถตรวจสอบความยาวของอาร์เรย์ในลักษณะเดียวกับข้างต้น


ปัญหาเดียวกันอาจช้าถ้ามีไฟล์จำนวนมาก ... แต่มันอาจจะเร็วกว่าที่ 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()
Jonathan Gilbert

3

คุณจะต้องไปที่ฮาร์ดไดรฟ์สำหรับข้อมูลนี้ไม่ว่าในกรณีใด ๆ และสิ่งนี้เพียงอย่างเดียวจะทำให้เกิดการสร้างวัตถุและการเติมอาร์เรย์


1
จริงแม้ว่าการสร้างวัตถุบางอย่างเกี่ยวข้องกับการค้นหาข้อมูลเมตาเพิ่มเติมบนดิสก์ที่อาจไม่จำเป็น
Adam Rosenfield

ACL จะต้องมีสำหรับวัตถุทุกอย่างแน่นอน ไม่มีทางรอบมัน และเมื่อคุณต้องค้นหาสิ่งเหล่านั้นคุณอาจอ่านข้อมูลอื่น ๆ ในส่วนหัวของ MFT สำหรับไฟล์ในโฟลเดอร์
Don Reba

3

ฉันไม่ทราบวิธีการที่จะบอกคุณอย่างชัดเจนว่าโฟลเดอร์ที่กำหนดมีโฟลเดอร์หรือไฟล์อื่น ๆ อย่างไรก็ตามโดยใช้:

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

ควรช่วยประสิทธิภาพเนื่องจากทั้งสองวิธีนี้จะส่งคืนอาร์เรย์ของสตริงด้วยชื่อของไฟล์ / ไดเรกทอรีแทนที่จะเป็นวัตถุ FileSystemInfo ทั้งหมด



2

ขอบคุณทุกคนสำหรับการตอบกลับ ฉันพยายามใช้Directory.GetFiles ()และDirectory.GetDirectories ()วิธีการ ข่าวดี! ประสิทธิภาพดีขึ้น ~ สองครั้ง! 229 สายใน 221ms แต่ฉันก็หวังว่ามันเป็นไปได้ที่จะหลีกเลี่ยงการแจงนับรายการทั้งหมดในโฟลเดอร์ ยอมรับว่ายังมีงานที่ไม่จำเป็นกำลังดำเนินการอยู่ คุณไม่คิดอย่างนั้นเหรอ?

หลังจากการตรวจสอบทั้งหมดฉันถึงข้อสรุปว่าภายใต้ pure .NET การปรับให้เหมาะสมต่อไปเป็นไปไม่ได้ ฉันจะเล่นกับฟังก์ชั่นFindFirstFileของ WinAPI หวังว่ามันจะช่วย


1
ด้วยเหตุผลใดที่คุณต้องการประสิทธิภาพสูงสำหรับการดำเนินการนี้
meandmycode

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

รหัสของฉันวิเศษมากมันใช้เวลา00: 00: 00.0007143 น้อยกว่าหนึ่งไมล์กับ 34 ไฟล์ในโฟลเดอร์

   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.