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

时间:2009-01-22 05:21:17

标签: c# .net windows

我已编写以下例程来手动遍历目录并在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中计算文件夹大小的最有效方法是什么?

22 个答案:

答案 0 :(得分:52)

不,这看起来像recommended way来计算目录大小,下面包含相关方法:

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是要计算的文件夹大小。

答案 1 :(得分:25)

我不相信有一个Win32 API来计算目录所占用的空间,尽管我要对此进行更正。如果有的话我会假设资源管理器会使用它。如果在资源管理器中获得大型目录的属性,则为您提供文件夹大小所需的时间与其包含的文件/子目录的数量成正比。

你的日常工作看起来相当整洁简单。请记住,您正在计算文件长度的总和,而不是磁盘上消耗的实际空间。群集,文件流等末尾的浪费空间所占用的空间将被忽略。

答案 2 :(得分:19)

最好的和最短的一线方式可以跟随

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

答案 3 :(得分:15)

真正的问题是,您打算使用的大小?

您的第一个问题是“文件大小”至少四个 定义:

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

  • “有效数据长度”,它等于实际未存储的第一个字节的偏移
    这始终小于或等于“文件结尾”,并且是簇大小的倍数 例如,1 GB文件的有效数据长度为1 MB。如果您要求Windows读取前8 MB,它将读取前1 MB并假装其余数据存在,并将其作为零返回。

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

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

您的第二个问题是C:\Foo之类的“文件”实际上可能有多个数据。
此名称仅引用默认流。文件可能包含备用流,例如C:\Foo:Bar,其大小甚至不会显示在资源管理器中!

您的第三个​​问题是“文件”有多个名称(“硬链接”)。
例如,C:\Windows\notepad.exeC:\Windows\System32\notepad.exe相同文件的两个名称任何名称都可用于打开文件的任何流。

您的第四个问题是“文件”(或目录)实际上可能甚至不是文件(或目录):
它可能是软链接(“符号链接”或“重新分析点”)到其他文件(或目录)。
那个其他文件甚至可能不在同一个驱动器上。它甚至可能指向网络上的某些东西,或者它甚至可能是递归的!如果它是递归的,那么大小应该是无限的吗?

您的第五个是有“过滤器”驱动程序,使某些文件或目录看起来像实际文件或目录,即使它们不是。例如,Microsoft的WIM图像文件(已压缩)可以使用名为ImageX的工具“挂载”在文件夹上,而那些看起来像重新分析点或链接。它们看起来就像目录 - 除了它们实际上不是目录,而“size”的概念对它们来说并不合理。

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

答案 4 :(得分:13)

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

答案 5 :(得分:5)

var size = new DirectoryInfo("E:\\").GetDirectorySize();

这是扩展方法背后的代码

public static long GetDirectorySize(this System.IO.DirectoryInfo directoryInfo, bool recursive = true)
{
    var startDirectorySize = default(long);
    if (directoryInfo == null || !directoryInfo.Exists)
        return startDirectorySize; //Return 0 while Directory does not exist.

    //Add size of files in the Current Directory to main size.
    foreach (var fileInfo in directoryInfo.GetFiles())
        System.Threading.Interlocked.Add(ref startDirectorySize, fileInfo.Length);

    if (recursive) //Loop on Sub Direcotries in the Current Directory and Calculate it's files size.
        System.Threading.Tasks.Parallel.ForEach(directoryInfo.GetDirectories(), (subDirectory) =>
    System.Threading.Interlocked.Add(ref startDirectorySize, GetDirectorySize(subDirectory, recursive)));

    return startDirectorySize;  //Return full Size of this Directory.
}

答案 6 :(得分:5)

更快!添加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());
          }

答案 7 :(得分:4)

我使用相同的计数主体扩展了@ Hao的答案,但支持更丰富的数据返回,因此您可以返回大小,递归大小,目录计数和递归目录计数,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; }
    }
}

答案 8 :(得分:4)

此解决方案效果很好。 它正在收集所有子文件夹:

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

答案 9 :(得分:4)

看来,以下方法比递归函数更快地执行任务:

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

一个简单的控制台应用程序测试表明,这个循环比递归函数更快地对文件求和,并提供相同的结果。您可能希望使用LINQ方法(如Sum())来缩短此代码。

答案 10 :(得分:4)

直到最近,我一直在调整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()

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

我不是C#专家,但您可以在C#this way上添加My命名空间。

我认为这种获取文件夹大小的方式不仅比在Hao link中描述的方式更短更现代,它基本上使用了最后描述的相同的FileInfo循环方法。

答案 11 :(得分:4)

这是计算目录大小的最佳方法。只有其他方式仍然会使用递归,但更容易使用,并不灵活。

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

答案 12 :(得分:3)

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

答案 13 :(得分:3)

Directory.GetFiles(@"C:\Users\AliBayat","*.*",SearchOption.AllDirectories)
.Select (d => new FileInfo(d))
.Select (d => new { Directory = d.DirectoryName,FileSize = d.Length} )
.ToLookup (d => d.Directory )
.Select (d => new { Directory = d.Key,TotalSizeInMB =Math.Round(d.Select (x =>x.FileSize)
.Sum () /Math.Pow(1024.0,2),2)})
.OrderByDescending (d => d.TotalSizeInMB).ToList();

GetFiles调用SearchOption.AllDirectories返回指定目录的所有subdirectories中所有文件的全名。 OS表示文件大小(以字节为单位)。您可以从文件的长度属性中检索文件的大小。用1024除以2的幂可以得到文件的大小(以兆字节为单位)。因为目录/文件夹可以包含许多文件,所以d.Select(x => x.FileSize)返回以兆字节为单位的文件大小的集合。对Sum()的最后一次调用将找到指定目录中文件的总大小。

答案 14 :(得分:2)

要提高性能,可以使用任务并行库(TPL)。 以下是一个很好的示例:Directory file size calculation - how to make it faster?

我没有测试它,但作者说它比非多线程方法快3倍......

答案 15 :(得分:2)

就最好的算法而言,你可能已经做对了。我建议您解开递归函数并使用自己的堆栈(记住堆栈溢出是.Net 2.0+应用程序中的世界末日,例外情况不能被捕获IIRC)。

最重要的是,如果您在任何形式的UI中使用它,请将其放在工作线程上,该线程向UI线程发出更新信号。

答案 16 :(得分:1)

我提出的最快的方法是使用带有SearchOption.AllDirectories的EnumerateFiles。此方法还允许在浏览文件和计算大小时更新UI。长路径名称不会导致任何问题,因为不会尝试为长路径名创建FileInfo或DirectoryInfo。在枚举文件时,即使文件名很长,只要起始目录名不太长,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());
        }
    }

答案 17 :(得分:1)

多线程示例可根据Microsoft Docs计算目录大小,这会更快

using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
      long totalSize = 0;

      String[] args = Environment.GetCommandLineArgs();
      if (args.Length == 1) {
         Console.WriteLine("There are no command line arguments.");
         return;
      }
      if (! Directory.Exists(args[1])) {
         Console.WriteLine("The directory does not exist.");
         return;
      }

      String[] files = Directory.GetFiles(args[1]);
      Parallel.For(0, files.Length,
                   index => { FileInfo fi = new FileInfo(files[index]);
                              long size = fi.Length;
                              Interlocked.Add(ref totalSize, size);
                   } );
      Console.WriteLine("Directory '{0}':", args[1]);
      Console.WriteLine("{0:N0} files, {1:N0} bytes", files.Length, totalSize);
   }
}
// The example displaysoutput like the following:
//       Directory 'c:\windows\':
//       32 files, 6,587,222 bytes

此示例仅计算当前文件夹中的文件,因此,如果要递归计算所有文件,则可以更改

String[] files = Directory.GetFiles(args[1]);

String[] files = Directory.GetFiles(args[1], "*", SearchOption.AllDirectories);

答案 18 :(得分:0)

此.NET Core命令行应用程序在此处计算给定路径的目录大小:

https://github.com/garethrbrown/folder-size

关键方法是这种方法,它递归检查子目录以得出总大小。

private static long DirectorySize(SortDirection sortDirection, DirectoryInfo directoryInfo, DirectoryData directoryData)
{
        long directorySizeBytes = 0;

        // Add file sizes for current directory

        FileInfo[] fileInfos = directoryInfo.GetFiles();

        foreach (FileInfo fileInfo in fileInfos)
        {
            directorySizeBytes += fileInfo.Length;
        }

        directoryData.Name = directoryInfo.Name;

        directoryData.SizeBytes += directorySizeBytes;

        // Recursively add subdirectory sizes

        DirectoryInfo[] subDirectories = directoryInfo.GetDirectories();

        foreach (DirectoryInfo di in subDirectories)
        {
            var subDirectoryData = new DirectoryData(sortDirection);

            directoryData.DirectoryDatas.Add(subDirectoryData);

            directorySizeBytes += DirectorySize(sortDirection, di, subDirectoryData);
        }

        directoryData.SizeBytes = directorySizeBytes;

        return directorySizeBytes;
    }
}

答案 19 :(得分:0)

我知道这不是.net解决方案,但是无论如何它都来了。也许对于拥有Windows 10并想要更快解决方案的人来说很方便。例如,如果您在命令提示符下运行此命令或通过按winKey + R

bash -c "du -sh /mnt/c/Users/; sleep 5"    

sleep 5是这样,因此您有时间查看结果并且窗口不会关闭

在我的计算机上显示:

enter image description here

最后请注意它如何显示85G(85 GB)。与使用.Net相比,它超级快速。如果您想更准确地查看尺寸,请删除h(代表人类可读)。

所以只需执行类似Processes.Start("bash",... arguments)的操作,那不是确切的代码,但是您可以理解。

答案 20 :(得分:0)

我尝试更改示例(Alexandre Pepin和hao的答案)

原样

    private long GetDirectorySize(string dirPath)
    {
        if (Directory.Exists(dirPath) == false)
        {
            return 0;
        }

        DirectoryInfo dirInfo = new DirectoryInfo(dirPath);

        long size = 0;

        // Add file sizes.
        FileInfo[] fis = dirInfo.GetFiles();
        foreach (FileInfo fi in fis)
        {
            size += fi.Length;
        }

        // Add subdirectory sizes.
        DirectoryInfo[] dis = dirInfo.GetDirectories();
        foreach (DirectoryInfo di in dis)
        {
            size += GetDirectorySize(di.FullName);
        }

        return size;
    }

成为

    private long GetDirectorySize2(string dirPath)
    {
        if (Directory.Exists(dirPath) == false)
        {
            return 0;
        }

        DirectoryInfo dirInfo = new DirectoryInfo(dirPath);

        long size = 0;

        // Add file sizes.
        IEnumerable<FileInfo> fis = dirInfo.EnumerateFiles("*.*", SearchOption.AllDirectories);
        foreach (FileInfo fi in fis)
        {
            size += fi.Length;
        }

        return size;
    }

最后您可以检查结果

        // ---------------------------------------------
        // size of directory
        using System.IO;

        string log1Path = @"D:\SampleDirPath1";
        string log2Path = @"D:\SampleDirPath2";
        string log1DirName = Path.GetDirectoryName(log1Path);
        string log2DirName = Path.GetDirectoryName(log2Path);
        long log1Size = GetDirectorySize(log1Path);
        long log2Size = GetDirectorySize(log2Path);
        long log1Size2 = GetDirectorySize2(log1Path);
        long log2Size2 = GetDirectorySize2(log2Path);

        Console.WriteLine($@"{log1DirName} Size: {SizeSuffix(log1Size)}, {SizeSuffix(log1Size2)}
        {log2DirName} Size: {SizeSuffix(log2Size)}, {SizeSuffix(log2Size2)}");

这是SizeSuffix函数

    private static readonly string[] SizeSuffixes =
               { "bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" };

    /// <summary>
    /// Size Display
    /// </summary>
    /// <param name="value">bytes 數值</param>
    /// <param name="decimalPlaces">小數位數</param>
    /// <returns></returns>
    public static string SizeSuffix(Int64 value, int decimalPlaces = 2)
    {
        if (decimalPlaces < 0) { throw new ArgumentOutOfRangeException("decimalPlaces"); }
        if (value < 0) { return "-" + SizeSuffix(-value); }
        if (value == 0) { return string.Format("{0:n" + decimalPlaces + "} bytes", 0); }

        // mag is 0 for bytes, 1 for KB, 2, for MB, etc.
        int mag = (int)Math.Log(value, 1024);

        // 1L << (mag * 10) == 2 ^ (10 * mag) 
        // [i.e. the number of bytes in the unit corresponding to mag]
        decimal adjustedSize = (decimal)value / (1L << (mag * 10));

        // make adjustment when the value is large enough that
        // it would round up to 1000 or more
        if (Math.Round(adjustedSize, decimalPlaces) >= 1000)
        {
            mag += 1;
            adjustedSize /= 1024;
        }

        return string.Format("{0:n" + decimalPlaces + "} {1}",
            adjustedSize,
            SizeSuffixes[mag]);
    }

答案 21 :(得分:-1)

Trikaldarshi的单行解决方案的替代方案。 (避免了构造FileInfo对象)

long sizeInBytes = Directory.EnumerateFiles("{path}","*", SearchOption.AllDirectories).Sum(fileInfo => fileInfo.Length);