计算文件夹大小/枚举文件系统
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
。
我正在尝试计算文件夹大小,但问题是;它对 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
。