Подтвердить что ты не робот

Какой лучший способ рассчитать размер каталога в .NET?

Я написал следующую процедуру для ручного перехода через каталог и вычисления его размера в С#/. 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?

4b9b3361

Ответ 1

Я не верю, что есть API-интерфейс Win32 для вычисления пространства, потребляемого каталогом, хотя я могу исправить это. Если бы это было тогда, я бы предположил, что Explorer будет использовать его. Если вы получаете свойства большой папки в проводнике, время, необходимое для предоставления вам размера папки, пропорционально количеству файлов/подкаталогов, которые оно содержит.

Ваша рутина выглядит довольно аккуратно и просто. Имейте в виду, что вы вычисляете сумму длин файлов, а не фактическое пространство, потребляемое на диске. Пространство, затрачиваемое пустым пространством в конце кластеров, файловыми потоками и т.д., Игнорируется.

Ответ 2

Нет, это выглядит как рекомендуемый способ, чтобы рассчитать размер каталога, соответствующий метод, приведенный ниже:

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

Вы вызывали бы с корнем как:

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

... где targetFolder - размер папки для вычисления.

Ответ 3

Лучший и самый короткий один лайнер может быть следующим

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

Ответ 4

Реальный вопрос: что вы намерены использовать для?

Ваша первая проблема заключается в том, что для "размера файла" есть как минимум четыре:

  • Смещение конца файла, которое представляет собой количество байтов, которые вы должны пропустить, чтобы перейти от начала до конца файла.
    Другими словами, это число байтов логически в файле (с точки зрения использования).

  • "Действительная длина данных", которая равна смещению первого байта, который фактически не хранится.
    Это всегда меньше или равно "концу файла" и кратно размеру кластера.
    Например, файл размером 1 ГБ может иметь допустимую длину данных 1 МБ. Если вы попросите Windows прочитать первые 8 МБ, она будет читать первые 1 МБ и делать вид, что остальная часть данных была там, возвращая ее как нули.

  • "Установленный размер" файла. Это всегда больше или равно "концу файла".
    Это количество кластеров, выделенных ОС для файла, умноженное на размер кластера.
    В отличие от случая, когда "конец файла" больше, чем "допустимая длина данных", избыточные байты не считаются частью данных файла, поэтому ОС не заполняет буфер нули, если вы попытаетесь прочитать в выделенной области за пределами файла.

  • "сжатый размер" файла, который действителен только для сжатых (и разреженных?) файлов.
    Он равен размеру кластера, умноженному на количество кластеров на томе, которые фактически распределены для этого файла.
    Для несжатых и не разреженных файлов нет понятия "сжатый размер"; вы вместо этого использовали бы "выделенный размер".

Вторая проблема заключается в том, что "файл" , такой как C:\Foo, может фактически иметь несколько потоков данных.
Это имя относится только к потоку по умолчанию. Файл может иметь альтернативные потоки, такие как C:\Foo:Bar, размер которых даже не отображается в Проводнике!

Ваша третья проблема заключается в том, что "файл" может иметь несколько имен ( "жесткие ссылки" ).
Например, C:\Windows\notepad.exe и C:\Windows\System32\notepad.exe - это два имени для одного и того же файла. Любое имя может быть использовано для открытия любого потока файла.

Ваша четвертая проблема заключается в том, что "файл" (или каталог) может фактически не быть файлом (или каталогом):
Это может быть мягкая ссылка ( "символическая ссылка" или "точка повторной обработки" ) на другой файл (или каталог).
Этот другой файл может даже не находиться на одном диске. Это может даже указать на что-то в сети, или это может быть даже рекурсивным! Должен ли размер быть бесконечным, если он рекурсивный?

В-пятых, есть драйверы "фильтра", которые делают определенные файлы или каталоги похожими на фактические файлы или каталоги, хотя они и не являются. Например, файлы изображений Microsoft WIM (которые сжаты) могут быть "смонтированы" в папке с помощью инструмента ImageX, и они не выглядят как точки повторной обработки или ссылки. Они выглядят так же, как каталоги, за исключением того, что на самом деле не каталоги, а понятие "размер" на самом деле не имеет для них смысла.

Ваша шестая проблема в том, что для каждого файла требуются метаданные.
Например, наличие 10 имен для одного и того же файла требует больше метаданных, для чего требуется пространство. Если имена файлов коротки, наличие 10 имен может быть столь же дешевым, как и 1 имя - и если они длинны, то наличие нескольких имен может использовать больше дискового пространства для метаданных. (Та же история с несколькими потоками и т.д.)
Вы тоже считаете их?

Ответ 5

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

Ответ 6

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

Ответ 7

Быстрее! Добавьте ссылку COM "Windows Script Объект хоста..."

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

Ответ 8

Это лучший способ рассчитать размер каталога. Только другой способ по-прежнему будет использовать рекурсию, но будет немного проще в использовании и не будет столь гибким.

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

Ответ 9

Я играл в 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.

Я не эксперт на С#, но вы можете добавить пространство имен My на С# таким образом.

Я думаю, что этот способ получения размера папки не только короче и современнее, чем способ, описанный в Hao ссылка, он в основном использует тот же метод loop-of-FileInfo, описанный там в конце.

Ответ 10

Я продлил ответ @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; }
    }
}

Ответ 11

Похоже, что следующий метод выполняет вашу задачу быстрее, чем рекурсивная функция:

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

Простой тест консольного приложения показывает, что этот цикл суммирует файлы быстрее, чем рекурсивная функция, и дает тот же результат. Вероятно, вы захотите использовать методы LINQ (например, Sum()), чтобы сократить этот код.

Ответ 12

это решение работает очень хорошо. он собирает все подпапки:

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

Ответ 13

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

Ответ 14

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 указанного каталога. ОС представляет размер файлов в байтах. Вы можете получить размер файла из его свойства Length. Разделив его на 1024 с точностью до 2, вы получите размер файла в мегабайтах. Поскольку каталог/папка может содержать много файлов, d.Select(x => x.FileSize) возвращает набор размеров файлов, измеренных в мегабайтах. Последний вызов Sum() находит общий размер файлов в указанном каталоге.

Обновление: filterMask = "." не работает с файлами без расширения

Ответ 15

Что касается лучшего алгоритма, вы, вероятно, имеете его право. Я бы порекомендовал вам разгадать рекурсивную функцию и использовать собственный стек (помните, что переполнение стека - это конец света в приложении .Net 2.0+, исключение нельзя поймать IIRC).

Самое главное, что если вы используете его в любом виде пользовательского интерфейса, поместите его в рабочий поток, который сигнализирует об обновлении потока пользовательского интерфейса.

Ответ 17

Самый быстрый способ, которым я пришел, - использовать EnumerateFiles с SearchOption.AllDirectories. Этот метод также позволяет обновлять интерфейс при просмотре файлов и подсчете размера. Длинные имена путей не вызывают никаких проблем, поскольку FileInfo или DirectoryInfo не создаются для имени длинного пути. При перечислении файлов, хотя имя файла длиннее, FileInfo, возвращаемый EnumerateFiles, не вызывает проблем, если имя начального каталога не слишком велико. По-прежнему существует проблема с 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());
        }
    }

Ответ 18

Это приложение командной строки ядра .NET здесь вычисляет размеры каталогов для заданного пути:

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

Альтернатива Trikaldarshi одной линии решения. (Это избавляет от необходимости создавать объекты FileInfo)

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

Ответ 20

Я знаю, что это не решение .net, но оно приходит в любом случае. Может быть, это пригодится людям, которые имеют Windows 10 и хотят более быстрого решения. Например, если вы запускаете эту команду в командной строке или нажимаете winKey + R:

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

sleep 5 так, что у вас есть время, чтобы увидеть результаты и окна не закрывается

На моем компьютере отображается:

enter image description here

Обратите внимание на то, как он показывает 85G (85 гигабайт). Это ужин по сравнению с .Net. Если вы хотите увидеть размер более точно, уберите h который обозначает удобочитаемый.

Так что просто сделайте что-то вроде Processes.Start("bash",... arguments) Это не точный код, но вы поняли идею.