计算文件夹大小/枚举文件系统

Calculating folder size / Enumerate filesystem

我正在尝试计算文件夹大小,但问题是;它对 D:\ 驱动器或其他文件夹运行速度很快,但每当我尝试单击 C:\ 驱动器时,应用程序会冻结大约 7-8 秒。 (我的驱动器列表在树视图上) 当我删除文件夹大小时,一切正常。你们有什么想法吗?

   public FolderModel(string folderPath)
    {
        try
        {

            //File = new FileInfo(folderPath);
            //FolderInfo = new DirectoryInfo(folderPath);
            //_createdTime = FolderInfo.CreationTime.ToShortDateString();
            //_folderName = FolderInfo.Name;
            //_folderPath = folderPath;
            //Fileextension = File.Extension.ToLower();
            //this.Children = new ObservableCollection<FolderModel>();

            _folderSize = CalculatorSize(GetDirectorySize(folderPath));
           
        }
        catch (Exception e)
        {
            //
        }
    }




    internal string CalculatorSize(long bytes)
    {
        var suffix = new[] { "B", "KB", "MB", "GB", "TB" };
        float byteNumber = bytes;
        for (var i = 0; i < suffix.Length; i++)
        {
            if (byteNumber < 1000)
            {
                if (i == 0)
                    return $"{byteNumber} {suffix[i]}";
                else
                    return $"{byteNumber:0.#0} {suffix[i]}";
            }
            else
            {
                byteNumber /= 1024;
            }
        }
        return $"{byteNumber:N} {suffix[suffix.Length - 1]}";
    }



    internal static long GetDirectorySize(string directoryPath)
    {
        try
        {
            if (Directory.Exists(directoryPath))
            {
                var d = new DirectoryInfo(directoryPath);
                return d.EnumerateFiles("*", SearchOption.AllDirectories).Sum(fi => fi.Length);
            }

            return new FileInfo(directoryPath).Length;
        }
        catch (UnauthorizedAccessException)
        {
            return 0;
        }
        catch (FileNotFoundException)
        {
            return 0;
        }
        catch (DirectoryNotFoundException)
        {
            return 0;
        }
    }

您必须在后台线程中枚举文件夹。

改进性能的建议
当使用 DriveInfo API 时,您可以进一步提高文件夹路径是驱动器的情况的性能。在这种情况下,您可以省略完整驱动器的枚举,这通常需要一段时间。
此外,当枚举抛出 UnauthorizedAccessException 异常时,您当前的实现会中止计算。你不想要那个。您希望算法忽略禁止的文件系统路径。

以下两个示例显示了您的实施的固定和改进版本。
第一个解决方案针对现代 .NET Standard 2.1 兼容的 .NET 版本。
第二种解决方案针对旧的 .NET Framework。

.NET 标准 2.1(.NET 核心 3.0、.NET 5)

使用与 .NET Standard 2.1 兼容的 .NET 版本(如 .NET Core 3.0 和 .NET 5)时,您可以消除异常处理。使用 EnumerationOptions 作为参数允许 API 忽略无法访问的目录,这显着提高了性能(不再有 UnauthorizedAccessException 异常)和可读性:

internal static async Task<bool> TryGetDirectorySize(string directoryPath, out long spaceUsedInBytes)
{
  spaceUsedInBytes = -1;
  var drives = DriveInfo.GetDrives();
  DriveInfo targetDrive = drives.FirstOrDefault(drive => drive.Name.Equals(directoryPath, StringComparison.OrdinalIgnoreCase));

  // Directory is a drive: skip the expensive enumeration of complete drive.
  if (targetDrive != null)
  {
    spaceUsedInBytes = targetDrive.TotalSize - targetDrive.TotalFreeSpace;
    return true;
  }

  if (!Directory.Exists(folderPath))
  {
    return false;
  }

  // Consider to make this local variable a private property
  var enumerationOptions = new EnumerationOptions { RecurseSubdirectories = true };

  var targetFolderInfo = new DirectoryInfo(directoryPath);
  spaceUsedInBytes = await Task.Run(
    () => targetFolderInfo.EnumerateFiles("*", enumerationOptions)
      .Sum(fileInfo => fileInfo.Length));

  return true;
}

.NET 框架

.NET Framework 兼容版本。它修复了原始代码的问题,即一旦抛出 UnauthorizedAccessException 异常,枚举就会中止。此版本继续使用递归枚举所有剩余目录:

internal static async Task<long> GetDirectorySize(string directoryPath)
{
  long spaceUsedInBytes = -1;
  var drives = DriveInfo.GetDrives();
  DriveInfo targetDrive =  drives.FirstOrDefault(drive => drive.Name.Equals(directoryPath, StringComparison.OrdinalIgnoreCase));

  // Directory is a drive: skip enumeration of complete drive.
  if (targetDrive != null)
  {
    spaceUsedInBytes = targetDrive.TotalSize - targetDrive.TotalFreeSpace;
    return spaceUsedInBytes;
  }

  var targetDirectoryInfo = new DirectoryInfo(directoryPath);
  spaceUsedInBytes = await Task.Run(() => SumDirectorySize(targetDirectoryInfo));
  return spaceUsedInBytes;
}

private static long SumDirectorySize(DirectoryInfo parentDirectoryInfo)
{
  long spaceUsedInBytes = 0;
  try
  {
    spaceUsedInBytes = parentDirectoryInfo.EnumerateFiles("*", SearchOption.TopDirectoryOnly)
      .Sum(fileInfo => fileInfo.Length);
  }
  catch (UnauthorizedAccessException)
  {
    return 0;
  }

  foreach (var subdirectoryInfo in parentDirectoryInfo.EnumerateDirectories("*", SearchOption.TopDirectoryOnly))
  {
    spaceUsedInBytes += SumDirectorySize(subdirectoryInfo);
  }

  return spaceUsedInBytes;
}

如何实例化需要运行构造异步操作的类型[​​=38=]

FolderModel.cs

class FolderModel
{
  // Make a constructor private to force instantiation using the factory method
  private FolderModel(string folderPath)
  {
    // Do non-async initialization
  }

  // Async factory method: add constructor parameters to async factory method
  public static async Task<FolderModel> CreateAsync(string folderPath)
  {
    var instance = new FolderModel(folderPath);
    await instance.InitializeAsync(folderPath);
    return instance;
  }

  // Define member as protected virtual to allow derived classes to add initialization routines
  protected virtual async Task InitializeAsync(string directoryPath)
  {
    // Consider to throw an exception here ONLY in case the folder is generated programmatically.
    // If folder is retrieved from user input, use input validation 
    // or even better use a folder picker dialog
    // to ensure that the provided path is always valid!
    if (!Directory.Exists(directoryPath))
    {
      throw new DirectoryNotFoundException($"Invalid directory path '{directoryPath}'.");
    }

    long folderSize = await GetDirectorySize(directoryPath);

    // TODO::Do something with the 'folderSize' value 
    // and execute other async code if necessary
  }
}

用法

// Create an instance of FolderModel example
private async Task SomeMethod()
{
  // Always await async methods (methods that return a Task).
  // Call static CreateAsync method instead of the constructor.
  FolderModel folderModel = await FolderModel.CreateAsync(@"C:\");
}

在更高级的场景中,当您想延迟初始化时,例如因为您想要避免分配现在不需要或永远不需要的昂贵资源,您可以在某个特定时调用实例 InitializeAsync引用依赖于这些资源的成员,或者您可以使构造函数和 InitializeAsync 方法 public 允许 class 的用户显式调用 InitializeAsync