什么是计算 Windows 文件夹大小最快的方法?

20

我需要计算数百个文件夹的大小,有些可能是10MB,而有些可能是10GB。我需要使用C#快速获取每个文件夹的大小。

我的最终结果希望是:

文件夹1 10.5GB

文件夹2 230MB

文件夹3 1.2GB

...


4
在C#中无法完成这个任务。C#没有访问文件系统的功能。您需要使用.NET Framework或Win32 API来完成此任务。 - John Saunders
6
@john:我认为“C#没有访问文件系统的功能”这句话可能会被误解,主要是因为System.IO。我知道你的意思,但对于路人来说,它可能会暗示其他含义。 - Ta01
1
@RandomNoob:我希望路人能够认识到C#和.NET框架是不同的事实。 - John Saunders
6
@John Saunders: 这是一个特别繁琐的观点。提问者已经打上了“文件系统”和“.net”的标签。如果我是一个VB.net程序员,我可能会用VB.net的术语来提出问题,以便获得用VB.net编写的答案,而不是C#的答案。 - JBRWilkinson
很遗憾,Windows文件系统不像MacOS那样作弊,它在每个目录中内部存储目录大小作为Filesystemobject,并在添加/删除文件/目录时更新该值。这样就不需要进行计算,只需读取该值...但我离题了 :-( - Paul Farry
显示剩余6条评论
9个回答

37

添加对 Microsoft Scripting Runtime 的引用,然后使用以下代码:

Scripting.FileSystemObject fso = new Scripting.FileSystemObject();
Scripting.Folder folder = fso.GetFolder([folder path]);
Int64 dirSize = (Int64)folder.Size;

如果您只需要大小,这比递归要快得多。


1
对于一个包含900K个文件且大小为9.5 GB的目录,使用这种方法所需时间为250毫秒,而递归方法需要约15秒。 - JRadness
该死!这个做法快了90%!绝对惊人!如果我可以投多次赞,我会这样做,因为这是一件惊人的事情。如果有关于此COM引用的问题,请留言。 - Akku
2
您也可以通过转到“添加引用”>“COM”>“Microsoft Scripting Runtime”来添加参考。 - Ricketts
我在传递驱动器号时遇到了问题,例如C:或D:。还有其他人遇到这个问题吗? - windowsgm
有人知道 API FileSystemObject.Size 使用了什么来获取/计算大小吗? - Jack Ukleja
显示剩余5条评论

13

好的,这很糟糕,但是...

使用一个名为dirsize.bat的递归dos批处理文件:

@ECHO OFF
IF %1x==x GOTO start
IF %1x==DODIRx GOTO dodir
SET CURDIR=%1
FOR /F "usebackq delims=" %%A IN (`%0 DODIR`) DO SET ANSWER=%%A %CURDIR%
ECHO %ANSWER%
GOTO end
:start
FOR /D %%D IN (*.*) DO CALL %0 "%%D"
GOTO end
:dodir
DIR /S/-C %CURDIR% | FIND "File(s)"
GOTO end
:end

注意:第5行最后一个“%%A”后应该有一个制表符,而不是空格。

这就是你要查找的数据。它可以很快地处理成千上万个文件。实际上,它可以在不到2秒钟的时间内扫描完我的整个硬盘。

按照以下方式执行文件dirsize | sort /R /+25,以便首先列出最大的目录。

祝好运。


1
对我来说似乎不起作用……当针对我的硬盘运行('cd C:/',然后'dirsize.bat')时,它需要大约一分钟的时间并回显“ECHO已禁用”9次。(关于所需时间,它不是SSD而是混合型硬盘,我有505k个文件和233k个文件夹) - Camilo Martin

1
你可以像这样做,但是在获取文件夹大小时没有快速=true的设置,你必须将文件大小相加。
    private static IDictionary<string, long> folderSizes;

    public static long GetDirectorySize(string dirName)
    {
        // use memoization to keep from doing unnecessary work
        if (folderSizes.ContainsKey(dirName))
        {
            return folderSizes[dirName];
        }

        string[] a = Directory.GetFiles(dirName, "*.*");

        long b = 0;
        foreach (string name in a)
        {
            FileInfo info = new FileInfo(name);
            b += info.Length;
        }

        // recurse on all the directories in current directory
        foreach (string d in Directory.GetDirectories(dirName))
        {
            b += GetDirectorySize(d);
        }

        folderSizes[dirName] = b;
        return b;
    }

    static void Main(string[] args)
    {
        folderSizes = new Dictionary<string, long>();
        GetDirectorySize(@"c:\StartingFolder");
        foreach (string key in folderSizes.Keys)
        {
            Console.WriteLine("dirName = " + key + " dirSize = " + folderSizes[key]);
        }

        // now folderSizes will contain a key for each directory (starting
        // at c:\StartingFolder and including all subdirectories), and
        // the dictionary value will be the folder size
    }

GetDirectorySize() 的初始调用在哪里?如果没有这个,代码什么也不做,因为 folderSizes 是空的。 - JBRWilkinson
另外,folderSizes 还将包含所有子目录,而似乎 OP 只想要顶层的大小。 - JBRWilkinson
@JBRWilkinson - 是的,在我的一次编辑中,我不小心删除了那个初始调用。感谢您指出这一点。字典将包含所有结果,但OP可以使用他/她需要的结果。 - dcp

1

如果你右键点击一个大目录,然后选择属性,你会发现计算大小需要相当长的时间... 我认为我们无法在这方面超越微软。你可以做的一件事是索引目录/子目录的大小,如果你要一遍又一遍地计算它们... 这将显著提高速度。

你可以使用类似于以下代码来递归计算C#中的目录大小:

static long DirSize(DirectoryInfo directory)
{
    long size = 0;

    FileInfo[] files = directory.GetFiles();
    foreach (FileInfo file in files)
    {
        size += file.Length;
    }

    DirectoryInfo[] dirs = directory.GetDirectories();

    foreach (DirectoryInfo dir in dirs)
    {
        size += DirSize(dir);
    }

    return size;
}

1
我找到的在4.0-4.5框架下计算磁盘上文件大小和数量的最快方法是:
using System.IO;
using System.Threading;
using System.Threading.Tasks;

class FileCounter
{
  private readonly int _clusterSize;
  private long _filesCount;
  private long _size;
  private long _diskSize;

  public void Count(string rootPath)
  {
    // Enumerate files (without real execution of course)
    var filesEnumerated = new DirectoryInfo(rootPath)
                              .EnumerateFiles("*", SearchOption.AllDirectories);
    // Do in parallel
    Parallel.ForEach(filesEnumerated, GetFileSize);
  }

  /// <summary>
  /// Get real file size and add to total
  /// </summary>
  /// <param name="fileInfo">File information</param>
  private void GetFileSize(FileInfo fileInfo)
  {
    Interlocked.Increment(ref _filesCount);
    Interlocked.Add(ref _size, fileInfo.Length);
  }
}

var fcount = new FileCounter("F:\\temp");
fcount.Count();

这种方法在 .net 平台上是我能找到的最好的方法。另外,如果您需要计算簇大小和实际磁盘大小,则可以执行以下操作:

using System.Runtime.InteropServices;

private long WrapToClusterSize(long originalSize)
    {
        return ((originalSize + _clusterSize - 1) / _clusterSize) * _clusterSize;
    }

private static int GetClusterSize(string rootPath)
    {
        int sectorsPerCluster = 0, bytesPerSector = 0, numFreeClusters = 0, totalNumClusters = 0;
        if (!GetDiskFreeSpace(rootPath, ref sectorsPerCluster, ref bytesPerSector, ref numFreeClusters,
                              ref totalNumClusters))
        {
            // Satisfies rule CallGetLastErrorImmediatelyAfterPInvoke.
            // see http://msdn.microsoft.com/en-us/library/ms182199(v=vs.80).aspx
            var lastError = Marshal.GetLastWin32Error();
            throw new Exception(string.Format("Error code {0}", lastError));
        }
        return sectorsPerCluster * bytesPerSector;
    }
[DllImport(Kernel32DllImport, SetLastError = true)]
    private static extern bool GetDiskFreeSpace(
        string rootPath,
        ref int sectorsPerCluster,
        ref int bytesPerSector,
        ref int numFreeClusters,
        ref int totalNumClusters);

当然,您需要在第一个代码段中重写GetFileSize()函数:

private long _diskSize;
private void GetFileSize(FileInfo fileInfo)
    {
        Interlocked.Increment(ref _filesCount);
        Interlocked.Add(ref _size, fileInfo.Length);
        Interlocked.Add(ref _diskSize, WrapToClusterSize(fileInfo.Length));
    }

1
在.Net中没有简单的方法来做到这一点;你需要循环遍历每个文件和子目录。 看一下这里的示例,了解如何实现。

在Windows中似乎也很慢,所以可能没有快速的方法,计算100个或1000个大文件夹的大小可能不可行 :-( - LeeW
在阅读了所有评论后,我决定不这样做了。虽然这是一个很好的功能,但开销太大了。谢谢大家。 - LeeW

1

Dot Net Pearls有一个类似于这里描述的方法。令人惊讶的是System.IO.DirectoryInfo类没有一个方法来做到这一点,因为它似乎是一个常见的需求,而且可能会更快地完成它,而不需要在每个文件系统对象上进行本机/托管转换。我认为,如果速度是关键,可以编写一个非托管对象来执行此计算,然后从托管代码中每个目录调用它一次。


0

这里有一些线索在这个链接(虽然是用Python写的),来自一个遇到类似性能问题的人。你可以尝试调用Win32 API来看看是否能提高性能,但最终你会遇到同样的问题:一个任务只能被执行得那么快,如果你需要多次执行该任务,那么它将需要很长时间。你能否详细说明一下你正在做什么?这可能有助于人们想出一些启发式方法或一些技巧来帮助你。如果你经常进行这种计算,你是否缓存了结果?


-1

我非常确定这个程序会慢得要死,但我会这样写:

using System.IO;

long GetDirSize(string dir) {
   return new DirectoryInfo(dir)
      .GetFiles("", SearchOption.AllDirectories)
      .Sum(p => p.Length);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接