在.NET中计算目录大小的最佳方法是什么?

我已经写了下面的例程手动遍历一个目录,并在C#/。NET中计算其大小:

protected static float CalculateFolderSize(string folder) { float folderSize = 0.0f; try { //Checks if the path is valid or not if (!Directory.Exists(folder)) return folderSize; else { try { foreach (string file in Directory.GetFiles(folder)) { if (File.Exists(file)) { FileInfo finfo = new FileInfo(file); folderSize += finfo.Length; } } foreach (string dir in Directory.GetDirectories(folder)) folderSize += CalculateFolderSize(dir); } catch (NotSupportedException e) { Console.WriteLine("Unable to calculate folder size: {0}", e.Message); } } } catch (UnauthorizedAccessException e) { Console.WriteLine("Unable to calculate folder size: {0}", e.Message); } return folderSize; } 

我有一个应用程序运行这个例程反复大量的文件夹。 我想知道是否有更有效的方法来计算一个文件夹的大小与.NET? 我没有看到任何具体的框架。 我应该使用P / Invoke和Win32 API吗? .NET中计算文件夹大小的最有效方法是什么?

我不相信有一个Win32 API来计算一个目录所消耗的空间,但我坚持要纠正这一点。 如果有的话,我会假设资源pipe理器将使用它。 如果您在资源pipe理器中获得大型目录的属性,则给予文件夹大小所需的时间与其包含的文件/子目录数成正比。

你的例程看起来相当简洁。 请记住,您正在计算文件长度的总和,而不是磁盘上消耗的实际空间。 集群末尾的浪费空间所消耗的空间,文件stream等都被忽略了。

不,这看起来像是计算目录大小的推荐方法 ,相关的方法如下:

 public static long DirSize(DirectoryInfo d) { long size = 0; // Add file sizes. FileInfo[] fis = d.GetFiles(); foreach (FileInfo fi in fis) { size += fi.Length; } // Add subdirectory sizes. DirectoryInfo[] dis = d.GetDirectories(); foreach (DirectoryInfo di in dis) { size += DirSize(di); } return size; } 

你会用root来调用:

 Console.WriteLine("The size is {0} bytes.", DirSize(new DirectoryInfo(targetFolder)); 

targetFolder是要计算的文件夹大小。

真正的问题是, 你打算用什么尺寸

你的第一个问题是“文件大小” 至less有四个定义:

  • “文件结束”偏移量,即从文件的开头到结尾必须跳过的字节数。
    换句话说,它是逻辑上在文件中的字节数(从使用的angular度来看)。

  • “有效数据长度”等于没有实际存储的第一个字节的偏移量。
    这总是小于或等于“文件结束”,并且是群集大小的倍数。
    例如,1 GB文件的有效数据长度为1 MB。 如果您要求Windows读取前8 MB,则会读取前1 MB,并假装其余数据在那里,并将其返回为零。

  • 文件的“分配大小”。 这总是大于或等于“文件的结尾”。
    这是操作系统为文件分配的集群数量,乘以集群大小。
    与“文件结束”大于“有效数据长度”的情况不同,超出的字节被认为是文件数据的一部分,因此如果您尝试读取数据,操作系统将不会使用零填充缓冲区分配的区域超出文件末尾。

  • 文件的“压缩大小”,仅对压缩文件(和稀疏文件)有效。
    它等于一个集群的大小,乘以实际分配给该文件的卷上的集群数量。
    对于非压缩和非稀疏文件,没有“压缩大小”的概念; 你会使用“分配的大小”来代替。

你的第二个问题是像C:\Foo这样的“文件”实际上可能有多个数据stream
这个名字只是指默认stream。 一个文件可能有替代stream,如C:\Foo:Bar ,其大小甚至没有在资源pipe理器中显示!

你的第三个问题是一个“文件”可以有多个名称 (“硬链接”)。
例如, C:\Windows\notepad.exeC:\Windows\System32\notepad.exe同一个文件的两个名称任何名字都可以用来打开文件的任何stream。

你的第四个问题是,一个“文件”(或目录)实际上可能不是一个文件(或目录):
它可能是某个其他文件(或目录)的软链接 (“符号链接”或“重parsing点”)。
其他文件甚至可能不在同一个驱动器上。 它甚至可能指向networking上的某些东西,甚至可能是recursion的! 如果它是recursion的,它的大小应该是无穷大的吗?

你的第五个是有“filter”驱动程序,使某些文件或目录看起来像实际的文件或目录,即使它们不是。 例如,微软的WIM图像文件(被压缩)可以使用名为ImageX的工具“挂载”到一个文件夹中,而这些文件看起来不像重分析点或链接。 他们看起来就像目录一样 – 除了“实际上不是目录”,“大小”的概念对他们来说并不合适。

你的第六个问题是每个文件都需要元数据。
例如,对同一个文件有10个名字需要更多的元数据,这就需要空间。 如果文件名很短,有10个名字可能就像有1个名字一样便宜 – 如果它们很长,那么具有多个名字可以为元数据使用更多的磁盘空间。 (与多个stream相同的故事等)
你也数这些吗?

 public static long DirSize(DirectoryInfo dir) { return dir.GetFiles().Sum(fi => fi.Length) + dir.GetDirectories().Sum(di => DirSize(di)); } 

最好和最短的class轮方式可以如下

  long length = Directory.GetFiles(directoryPath,"*",SearchOption.AllDirectories).Sum(t => (new FileInfo(t).Length)); 

更多更快! 添加COM参考“Windows脚本宿主对象…”

 public double GetWSHFolderSize(string Fldr) { //Reference "Windows Script Host Object Model" on the COM tab. IWshRuntimeLibrary.FileSystemObject FSO = new IWshRuntimeLibrary.FileSystemObject(); double FldrSize = (double)FSO.GetFolder(Fldr).Size; Marshal.FinalReleaseComObject(FSO); return FldrSize; } private void button1_Click(object sender, EventArgs e) { string folderPath = @"C:\Windows"; Stopwatch sWatch = new Stopwatch(); sWatch.Start(); double sizeOfDir = GetWSHFolderSize(folderPath); sWatch.Stop(); MessageBox.Show("Directory size in Bytes : " + sizeOfDir + ", Time: " + sWatch.ElapsedMilliseconds.ToString()); } 
 public static long GetDirectorySize(string fullDirectoryPath, bool recursive = true) { long startDirectorySize = 0; if (!Directory.Exists(fullDirectoryPath)) return startDirectorySize; //Return 0 while Directory does not exist. var currentDirectory = new DirectoryInfo(fullDirectoryPath); //Add size of files in the Current Directory to main size. foreach (var file in currentDirectory.GetFiles()) System.Threading.Interlocked.Add(ref startDirectorySize, file.Length); if (recursive) //Loop on Sub Direcotries in the Current Directory and Calculate it's files size. System.Threading.Tasks.Parallel.ForEach(currentDirectory.GetDirectories(), (subDirectory) => System.Threading.Interlocked.Add(ref startDirectorySize, GetDirectorySize(subDirectory.FullName, recursive))); return startDirectorySize; //Return full Size of this Directory. } 

(直到最近我一直在VS2008和LINQ的摆弄),这个小巧简洁的方法对我很好(例如在VB.NET中,当然需要LINQ / .NET FW 3.5+):

 Dim size As Int64 = (From strFile In My.Computer.FileSystem.GetFiles(strFolder, _ FileIO.SearchOption.SearchAllSubDirectories) _ Select New System.IO.FileInfo(strFile).Length).Sum() 

简而言之,它会search子目录,并且如果您知道LINQ语法,则很容易理解。 您甚至可以使用.GetFiles函数的第三个参数指定通配符来search特定的文件。

我不是一个C#专家,但你可以添加我的命名空间在C# 这种方式 。

我认为这种获取文件夹大小的方式不仅比郝的链接上描述的方式更短,也更现代,它基本上使用了与此处描述的相同的FileInfo循环方法。

这是计算目录大小的最好方法。 只有其他的方式仍然会使用recursion,但是使用起来更容易一些,并且不那么灵活。

 float folderSize = 0.0f; FileInfo[] files = Directory.GetFiles(folder, "*", SearchOption.AllDirectories); foreach(FileInfo file in files) folderSize += file.Length; 

看来,下面的方法比recursion函数更快地执行你的任务:

 long size = 0; DirectoryInfo dir = new DirectoryInfo(folder); foreach (FileInfo fi in dir.GetFiles("*.*", SearchOption.AllDirectories)) { size += fi.Length; } 

一个简单的控制台应用程序testing表明,这个循环比recursion函数更快地求和文件,并提供相同的结果。 你可能想要使用LINQ方法(比如Sum())来缩短这段代码。

 public static long GetDirSize(string path) { try { return Directory.EnumerateFiles(path).Sum(x => new FileInfo(x).Length) + Directory.EnumerateDirectories(path).Sum(x => GetDirSize(x)); } catch { return 0L; } } 

这个解决scheme工作得很好。 它正在收集所有的子文件夹:

 Directory.GetFiles(@"MainFolderPath", "*", SearchOption.AllDirectories).Sum(t => (new FileInfo(t).Length)); 

至于最好的algorithm,你可能是正确的。 我build议你解开recursion函数并使用你自己的堆栈(记住堆溢出是.Net 2.0+应用程序中的世界末日,这个exception不能被抓到IIRC)。

最重要的是,如果您以任何forms的UI使用它,请将其放置在一个工作线程上,以向UI线程发送更新信号。

为了提高性能,可以使用任务并行库(TPL)。 下面是一个很好的示例: 目录文件大小计算 – 如何使其更快?

我没有testing它,但作者说这是一个非multithreading方法的3倍…

我扩展@浩的答案使用相同的计数主体,但支持更丰富的数据返回,所以你回到大小,recursion大小,目录计数和recursion目录计数,N级深。

 public class DiskSizeUtil { /// <summary> /// Calculate disk space usage under <paramref name="root"/>. If <paramref name="levels"/> is provided, /// then return subdirectory disk usages as well, up to <paramref name="levels"/> levels deep. /// If levels is not provided or is 0, return a list with a single element representing the /// directory specified by <paramref name="root"/>. /// </summary> /// <returns></returns> public static FolderSizeInfo GetDirectorySize(DirectoryInfo root, int levels = 0) { var currentDirectory = new FolderSizeInfo(); // Add file sizes. FileInfo[] fis = root.GetFiles(); currentDirectory.Size = 0; foreach (FileInfo fi in fis) { currentDirectory.Size += fi.Length; } // Add subdirectory sizes. DirectoryInfo[] dis = root.GetDirectories(); currentDirectory.Path = root; currentDirectory.SizeWithChildren = currentDirectory.Size; currentDirectory.DirectoryCount = dis.Length; currentDirectory.DirectoryCountWithChildren = dis.Length; currentDirectory.FileCount = fis.Length; currentDirectory.FileCountWithChildren = fis.Length; if (levels >= 0) currentDirectory.Children = new List<FolderSizeInfo>(); foreach (DirectoryInfo di in dis) { var dd = GetDirectorySize(di, levels - 1); if (levels >= 0) currentDirectory.Children.Add(dd); currentDirectory.SizeWithChildren += dd.SizeWithChildren; currentDirectory.DirectoryCountWithChildren += dd.DirectoryCountWithChildren; currentDirectory.FileCountWithChildren += dd.FileCountWithChildren; } return currentDirectory; } public class FolderSizeInfo { public DirectoryInfo Path { get; set; } public long SizeWithChildren { get; set; } public long Size { get; set; } public int DirectoryCount { get; set; } public int DirectoryCountWithChildren { get; set; } public int FileCount { get; set; } public int FileCountWithChildren { get; set; } public List<FolderSizeInfo> Children { get; set; } } } 

我想到的最快的方法是使用EnumerateFiles和SearchOption.AllDirectories。 该方法还允许在遍历文件并计算大小的同时更新UI。 长path名不会导致任何问题,因为FileInfo或DirectoryInfo不会尝试为长path名创build。 尽pipe枚举文件即使文件名很长,EnumerateFiles返回的FileInfo也不会导致问题,只要起始目录名称不太长。 UnauthorizedAccess仍然存在问题。

  private void DirectoryCountEnumTest(string sourceDirName) { // Get the subdirectories for the specified directory. long dataSize = 0; long fileCount = 0; string prevText = richTextBox1.Text; if (Directory.Exists(sourceDirName)) { DirectoryInfo dir = new DirectoryInfo(sourceDirName); foreach (FileInfo file in dir.EnumerateFiles("*", SearchOption.AllDirectories)) { fileCount++; try { dataSize += file.Length; richTextBox1.Text = prevText + ("\nCounting size: " + dataSize.ToString()); } catch (Exception e) { richTextBox1.AppendText("\n" + e.Message); } } richTextBox1.AppendText("\n files:" + fileCount.ToString()); } }