Làm thế nào để nhanh chóng kiểm tra nếu thư mục trống (.NET)?


140

Tôi phải kiểm tra, nếu thư mục trên đĩa trống. Nó có nghĩa là, nó không chứa bất kỳ thư mục / tập tin. Tôi biết, đó là một phương pháp đơn giản. Chúng tôi nhận được mảng của FileSystemInfo và kiểm tra xem số phần tử có bằng không. Một cái gì đó như thế:

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

Cách tiếp cận này có vẻ ổn. NHƯNG!! Đó là rất, rất xấu từ góc độ hiệu suất. GetFileSystemInfos () là một phương pháp rất khó. Trên thực tế, nó liệt kê tất cả các đối tượng hệ thống tập tin của thư mục, nhận tất cả các thuộc tính của chúng, tạo các đối tượng, điền vào mảng đã nhập, v.v. Và tất cả điều này chỉ để kiểm tra Độ dài. Điều đó thật ngu ngốc phải không?

Tôi vừa lập hồ sơ mã như vậy và xác định rằng ~ 250 cuộc gọi của phương thức đó được thực hiện trong ~ 500ms. Điều này rất chậm và tôi tin rằng có thể thực hiện nhanh hơn nhiều.

Bất kỳ đề xuất?


7
Vì tò mò, tại sao bạn muốn kiểm tra thư mục 250 lần?
ya23

2
@ ya23 Tôi cho rằng một người muốn kiểm tra 250 thư mục differents. Không một lần 250 lần.
Mathieu Pagé

Câu trả lời:


282

Có một tính năng mới trong DirectoryDirectoryInfotrong .NET 4 cho phép họ trả về một IEnumerablethay vì một mảng và bắt đầu trả về kết quả trước khi đọc tất cả nội dung thư mục.

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

EDIT: thấy câu trả lời đó một lần nữa, tôi nhận ra mã này có thể được thực hiện đơn giản hơn nhiều ...

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

Tôi thích giải pháp này, nó có thể được thực hiện để chỉ kiểm tra một số loại tệp nhất định không? .Contains ("jpg") thay vì .any () dường như không hoạt động
Dennis

5
@Dennis, bạn có thể chỉ định mẫu ký tự đại diện trong lệnh gọi EnumerateFileSystemEntrieshoặc sử dụng .Any(condition)(chỉ định điều kiện dưới dạng biểu thức lambda hoặc làm phương thức lấy đường dẫn làm tham số).
Thomas Levesque

Kiểu chữ có thể được loại bỏ khỏi ví dụ mã đầu tiên:return !items.GetEnumerator().MoveNext();
gary

1
@, nếu bạn làm điều đó, điều tra viên sẽ không được xử lý, vì vậy nó sẽ khóa thư mục cho đến khi điều tra viên được thu thập rác.
Thomas Levesque

Điều này có vẻ hoạt động tốt đối với các Thư mục chứa Tệp, nhưng nếu Danh mục chứa các Giám đốc khác, nó sẽ quay lại nói rằng nó trống.
Kairan

32

Đây là giải pháp cực nhanh, cuối cùng tôi đã thực hiện. Ở đây tôi đang sử dụng WinAPI và các hàm FindFirstFile , FindNextFile . Nó cho phép tránh liệt kê tất cả các mục trong Thư mục và dừng ngay sau khi phát hiện đối tượng đầu tiên trong Thư mục . Cách tiếp cận này nhanh hơn ~ 6 (!!) lần so với mô tả ở trên. 250 cuộc gọi trong 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();
}

Tôi hy vọng nó sẽ hữu ích cho ai đó trong tương lai.


Cảm ơn bạn đã chia sẻ giải pháp của bạn.
Greg

3
Bạn cần thêm SetLastError = truevào DllImportfor FindFirstFileđể Marshal.GetHRForLastWin32Error()cuộc gọi hoạt động chính xác, như được mô tả trong phần Ghi chú của tài liệu MSDN cho GetHRForLastWin32Error () .
Joel V. tha thiết-DeYoung

Tôi nghĩ rằng câu trả lời sau là tốt hơn một chút vì nó cũng tìm kiếm các tập tin trong thư mục con stackoverflow.com/questions/724148/
Kẻ

21

Bạn có thể thử Directory.Exists(path)Directory.GetFiles(path)- có lẽ ít chi phí hơn (không có đối tượng - chỉ có chuỗi, v.v.).


Như mọi khi, bạn nhanh nhất tắt cò! Đánh tôi vài giây đấy! :-)
Cerebrus

Cả hai bạn đều nhanh hơn tôi ... chết tiệt, tôi chú ý đến chi tiết ;-)
Eoin Campbell

2
Mặc dù vậy, tôi đã không làm điều gì tốt; câu trả lời đầu tiên và là câu duy nhất không có phiếu bầu ;-(
Marc Gravell

Không có tiền tố ... ai đó có một cái rìu để mài, methinks
Marc Gravell

1
Tôi không nghĩ GetFiles sẽ nhận được một danh sách các Thư mục, vì vậy có vẻ là một ý tưởng tốt để đưa vào kiểm tra cho 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);
}

Thử nghiệm nhanh này đã quay lại sau 2 mili giây cho thư mục khi trống và khi chứa các thư mục con & tệp (mỗi 5 thư mục có 5 tệp)


3
Bạn có thể cải thiện điều này bằng cách quay lại nếu 'dirs' không trống ngay lập tức mà không cần phải lấy danh sách các tệp.
samjudson

3
Có, nhưng nếu có hàng ngàn tệp trong đó thì sao?
Thomas Levesque

3
Bạn cũng đang đo thời gian để ghi vào bàn điều khiển, không đáng kể.
ctusch

11

Tôi sử dụng điều này cho các thư mục và tệp (không biết nếu nó tối ưu)

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

8

Nếu bạn không muốn rời khỏi C # thuần túy và thực hiện các cuộc gọi WinApi , thì bạn có thể muốn xem xét hàm PathIsDirectoryEmpty () . Theo MSDN, chức năng:

Trả về TRUE nếu pszPath là một thư mục trống. Trả về FALSE nếu pszPath không phải là một thư mục hoặc nếu nó chứa ít nhất một tệp khác ngoài "." hoặc là "..".

Đó dường như là một chức năng thực hiện chính xác những gì bạn muốn, vì vậy nó có thể được tối ưu hóa tốt cho nhiệm vụ đó (mặc dù tôi chưa thử nghiệm điều đó).

Để gọi nó từ C #, trang web pinvoke.net sẽ giúp bạn. (Thật không may, nó chưa mô tả chức năng nhất định này, nhưng bạn sẽ có thể tìm thấy một số hàm có đối số tương tự và loại trả về đó và sử dụng chúng làm cơ sở cho cuộc gọi của bạn. Nếu bạn nhìn lại vào MSDN, nó nói rằng DLL để nhập từ là shlwapi.dll)


Ý tưởng tuyệt vời. Tôi không biết về chức năng này. Tôi sẽ cố gắng so sánh hiệu suất của nó với cách tiếp cận của tôi, mà tôi đã mô tả ở trên. Nếu nó làm nhanh hơn, tôi sẽ sử dụng lại nó trong mã của mình. Cảm ơn.
zhe

4
Một lưu ý cho những người muốn đi tuyến đường này. Có vẻ như phương thức PathIsDirectoryEmpty () từ shlwapi.dll này hoạt động tốt trên các máy Vista32 / 64 và XP32 / 64, nhưng đánh bom trên một số máy Win7. Nó phải là một cái gì đó để làm với các phiên bản của shlwapi.dll được cung cấp với các phiên bản Windows khác nhau. Coi chừng.
Alex_P

7

Tôi không biết về thống kê hiệu suất trên trang này, nhưng bạn đã thử sử dụng Directory.GetFiles()phương pháp tĩnh chưa?

Nó trả về một mảng chuỗi chứa tên tệp (không phải FileInfos) và bạn có thể kiểm tra độ dài của mảng theo cách tương tự như trên.


cùng một vấn đề, nó có thể chậm nếu có nhiều tệp ... nhưng có lẽ nhanh hơn GetFileSystemInfos
Thomas Levesque

4

Tôi chắc rằng các câu trả lời khác nhanh hơn và câu hỏi của bạn hỏi liệu thư mục có chứa tệp hoặc thư mục hay không ... nhưng tôi nghĩ hầu hết mọi người sẽ xem xét một thư mục trống nếu nó không chứa tệp. tức là nó vẫn "trống" đối với tôi nếu nó chứa các thư mục con trống ... điều này có thể không phù hợp với mục đích sử dụng của bạn, nhưng có thể cho người khác!

  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

Bạn sẽ phải đi vào ổ cứng cho thông tin này trong mọi trường hợp, và điều này một mình sẽ thổi phồng bất kỳ việc tạo đối tượng và điền mảng.


1
Đúng, mặc dù việc tạo một số đối tượng liên quan đến việc tìm kiếm siêu dữ liệu bổ sung trên đĩa có thể không cần thiết.
Adam Rosenfield

ACL sẽ được yêu cầu cho mọi đối tượng chắc chắn. Không có cách nào xung quanh nó. Và một khi bạn phải tra cứu chúng, bạn cũng có thể đọc bất kỳ thông tin nào khác trong các tiêu đề MFT cho các tệp trong thư mục.
Don Reba

3

Tôi không biết về một phương pháp sẽ cho bạn biết một cách ngắn gọn nếu một thư mục đã cho có chứa bất kỳ thư mục hoặc tệp nào khác, tuy nhiên, bằng cách sử dụng:

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

sẽ giúp hiệu năng vì cả hai phương thức này sẽ chỉ trả về một chuỗi các chuỗi với tên của các tệp / thư mục chứ không phải toàn bộ các đối tượng FileSystemInfo.


2

Cảm ơn mọi người, đã trả lời. Tôi đã thử sử dụng Directory.GetFiles ()Directory.GetDirectories () các phương thức . Tin tốt! Hiệu suất được cải thiện ~ hai lần! 229 cuộc gọi trong 221ms. Nhưng tôi cũng hy vọng rằng có thể tránh việc liệt kê tất cả các mục trong thư mục. Đồng ý, đó vẫn là công việc không cần thiết đang thực hiện. Bạn không nghĩ vậy sao?

Sau tất cả các cuộc điều tra, tôi đã đi đến một kết luận, rằng dưới tối ưu .NET thuần túy là không thể. Tôi sẽ chơi với chức năng FindFirstFile của WinAPI . Hy vọng nó sẽ giúp.


1
Không quan tâm, những lý do bạn cần hiệu suất cao như vậy cho hoạt động này là gì?
meandmycode

1
Thay vì trả lời câu hỏi của riêng bạn, hãy đánh dấu một trong những câu trả lời đúng là câu trả lời (có thể là câu đầu tiên được đăng hoặc câu trả lời rõ ràng nhất). Bằng cách này, người dùng tương lai của stackoverflow sẽ thấy câu trả lời tốt nhất ngay dưới câu hỏi của bạn!
Ray Hayes

2

Đôi khi bạn có thể muốn xác minh xem có tệp nào tồn tại trong các thư mục con hay không và bỏ qua các thư mục con trống đó; trong trường hợp này bạn có thể sử dụng phương pháp dưới đây:

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

2

Dễ dàng và đơn giản:

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

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

0

Dựa trên mã Brad park :

    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

Mã của tôi thật tuyệt vời, nó chỉ mất 00: 00: 00.0007143 ít hơn milisecond với 34 tệp trong thư mục

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

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

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

Trên thực tế, nếu bạn nhân nó với 229 và thêm GetDirectories (), bạn sẽ nhận được kết quả tương tự, như của tôi :)
zhe

-1

Đây là một cái gì đó có thể giúp bạn làm điều đó. Tôi quản lý để làm điều đó trong hai lần lặp.

 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

Vì dù sao bạn cũng sẽ làm việc với một đối tượng DirectoryInfo, tôi sẽ đi với một phần mở rộng

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

-2

Dùng cái này. Thật đơn giản.

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
Đơn giản, có lẽ. Nhưng không chính xác. Nó có hai lỗi lớn: Nó không phát hiện nếu có bất kỳ thư mục nào trong đường dẫn, chỉ có các tệp và nó sẽ đưa ra một ngoại lệ trên một đường dẫn không tồn tại. Nó cũng có khả năng thực sự chậm hơn bản gốc của OP, vì tôi khá chắc chắn rằng nó nhận được tất cả các mục và lọc chúng.
Andrew Barber
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.