¿Cuál es la mejor manera de calcular el tamaño de un directorio en .NET?

He escrito la siguiente rutina para recorrer manualmente un directorio y calcular su tamaño en 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; } 

Tengo una aplicación que ejecuta esta rutina repetidamente para una gran cantidad de carpetas. Me pregunto si hay una manera más eficiente de calcular el tamaño de una carpeta con .NET. No vi nada específico en el marco. ¿Debo usar P / Invoke y una API de Win32? ¿Cuál es la forma más eficiente de calcular el tamaño de una carpeta en .NET?

No creo que haya una API de Win32 para calcular el espacio consumido por un directorio, aunque puedo corregirlo. Si hubiera, entonces asumiría que Explorer lo usaría. Si obtiene las Propiedades de un directorio grande en el Explorador, el tiempo necesario para darle el tamaño de la carpeta es proporcional a la cantidad de archivos / subdirectorios que contiene.

Tu rutina parece bastante clara y simple. Tenga en cuenta que está calculando la sum de las longitudes de archivo, no el espacio real consumido en el disco. Se está ignorando el espacio consumido por el espacio desperdiciado al final de los clústeres, flujos de archivos, etc.

No, esta parece ser la forma recomendada de calcular el tamaño del directorio, el método relevante se incluye a continuación:

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

Llamarías con la raíz como:

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

… donde targetFolder es el tamaño de la carpeta para calcular.

La mejor y más corta forma de revestimiento podría ser seguir

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

La verdadera pregunta es, ¿para qué pretendes usar el tamaño ?

Su primer problema es que hay al menos cuatro definiciones para “tamaño de archivo”:

  • Desplazamiento de “fin de archivo”, que es el número de bytes que debe omitir para ir desde el principio hasta el final del archivo.
    En otras palabras, es el número de bytes lógicamente en el archivo (desde una perspectiva de uso).

  • La “longitud de datos válida”, que es igual al desplazamiento del primer byte que en realidad no está almacenado .
    Esto siempre es menor o igual que el “fin del archivo”, y es un múltiplo del tamaño del clúster.
    Por ejemplo, un archivo de 1 GB puede tener una longitud de datos válida de 1 MB. Si le pides a Windows que lea los primeros 8 MB, leerá los primeros 1 MB y simulará que el rest de los datos estaba allí, y los devolverá como ceros.

  • El “tamaño asignado” de un archivo. Esto siempre es mayor o igual que el “fin del archivo”.
    Este es el número de clústeres que el sistema operativo ha asignado para el archivo, multiplicado por el tamaño del clúster.
    A diferencia del caso donde el “final del archivo” es mayor que la “longitud de datos válida”, los bytes en exceso no se consideran parte de los datos del archivo, por lo que el sistema operativo no rellenará un búfer con ceros si intenta leer en la región asignada más allá del final del archivo.

  • El “tamaño comprimido” de un archivo, que solo es válido para archivos comprimidos (y ¿dispersos?).
    Es igual al tamaño de un clúster, multiplicado por la cantidad de clústeres en el volumen que realmente están asignados a este archivo.
    Para archivos no comprimidos y no dispersos, no existe la noción de “tamaño comprimido”; en cambio, usaría el “tamaño asignado”.

El segundo problema es que un “archivo” como C:\Foo puede tener múltiples flujos de datos.
Este nombre solo se refiere a la transmisión predeterminada . Un archivo puede tener secuencias alternativas , como C:\Foo:Bar , cuyo tamaño ni siquiera se muestra en el Explorador.

Su tercer problema es que un “archivo” puede tener varios nombres (“enlaces duros”).
Por ejemplo, C:\Windows\notepad.exe y C:\Windows\System32\notepad.exe son dos nombres para el mismo archivo. Se puede usar cualquier nombre para abrir cualquier secuencia del archivo.

El cuarto problema es que un “archivo” (o directorio) podría no ser ni siquiera un archivo (o directorio):
Puede ser un enlace suave (un “enlace simbólico” o un “punto de reanálisis”) a algún otro archivo (o directorio).
Ese otro archivo puede que ni siquiera esté en la misma unidad. ¡Podría incluso señalar algo en la red, o incluso podría ser recursivo! ¿El tamaño debe ser infinito si es recursivo?

El quinto es que hay controladores de “filtro” que hacen que ciertos archivos o directorios parezcan archivos o directorios reales, aunque no lo sean. Por ejemplo, los archivos de imagen WIM de Microsoft (que están comprimidos) se pueden “montar” en una carpeta usando una herramienta llamada ImageX, y esos no se parecen a los puntos de reanálisis o enlaces. Se parecen a los directorios, excepto que no son en realidad directorios, y la noción de “tamaño” realmente no tiene sentido para ellos.

Tu sexto problema es que cada archivo requiere metadatos.
Por ejemplo, tener 10 nombres para el mismo archivo requiere más metadatos, lo que requiere espacio. Si los nombres de los archivos son cortos, tener 10 nombres puede ser tan barato como tener 1 nombre, y si son largos, tener varios nombres puede usar más espacio en disco para los metadatos . (Misma historia con múltiples transmisiones, etc.)
¿También cuentas esto?

 public static long DirSize(DirectoryInfo dir) { return dir.GetFiles().Sum(fi => fi.Length) + dir.GetDirectories().Sum(di => DirSize(di)); } 
 var size = new DirectoryInfo("E:\\").GetDirectorySize(); 

y aquí está el código detrás de este método de extensión

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

¡Más rápido! Agregue la referencia COM “Objeto de host de 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()); } 

He estado jugueteando con VS2008 y LINQ hasta hace poco y este método compacto y corto funciona muy bien para mí (por ejemplo, en VB.NET, requiere LINQ / .NET FW 3.5+ por supuesto):

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

Es corto, busca subdirectorios y es simple de entender si conoce la syntax de LINQ. Incluso podría especificar comodines para buscar archivos específicos utilizando el tercer parámetro de la función .GetFiles.

No soy un experto en C #, pero puede agregar el espacio de nombres Mi en C # de esta manera .

Creo que esta forma de obtener un tamaño de carpeta no solo es más corta y más moderna que la forma descrita en el enlace de Hao, sino que básicamente utiliza el mismo método loop-of-FileInfo descrito allí al final.

Esta es la mejor manera de calcular el tamaño de un directorio. Solo de otra manera todavía se usaría la recursión, pero sería un poco más fácil de usar y no tan flexible.

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

Extendí la respuesta de @ Hao utilizando el mismo principio de recuento, pero con el respaldo de una devolución de datos más completa, para que pueda recuperar el tamaño, el tamaño recursivo, el recuento de directorios y el recuento recuente de directorios, N niveles profundos.

 public class DiskSizeUtil { ///  /// Calculate disk space usage under . If  is provided, /// then return subdirectory disk usages as well, up to  levels deep. /// If levels is not provided or is 0, return a list with a single element representing the /// directory specified by . ///  ///  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(); 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 Children { get; set; } } } 

Parece que el siguiente método realiza su tarea más rápido que la función recursiva:

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

Una simple prueba de aplicación de consola muestra que este bucle sum los archivos más rápido que la función recursiva y proporciona el mismo resultado. Y probablemente desee utilizar métodos LINQ (como Sum ()) para acortar este código.

esta solución funciona muy bien. está recostackndo todas las subcarpetas:

 Directory.GetFiles(@"MainFolderPath", "*", SearchOption.AllDirectories).Sum(t => (new FileInfo(t).Length)); 
 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; } } 
 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(); 

Llamar a GetFiles con SearchOption.AllDirectories devuelve el nombre completo de todos los archivos en todos los subdirectories del directorio especificado. El sistema operativo representa el tamaño de los archivos en bytes. Puede recuperar el tamaño del archivo desde su propiedad Length. Dividirlo por 1024 elevado a la potencia de 2 le da el tamaño del archivo en megabytes. Como un directorio / carpeta puede contener muchos archivos, d.Select(x => x.FileSize) devuelve una colección de tamaños de archivos medidos en megabytes. La llamada final a Sum() encuentra el tamaño total de los archivos en el directorio especificado.

En cuanto al mejor algoritmo, probablemente lo tengas bien. Le recomendaría que desenmarañe la función recursiva y use una stack propia (recuerde que un desbordamiento de stack es el fin del mundo en una aplicación .Net 2.0+, la excepción no puede ser atrapada IIRC).

Lo más importante es que, si lo está utilizando en cualquier forma de interfaz de usuario, colóquelo en un hilo de trabajo que indique el hilo de la interfaz de usuario con actualizaciones.

Para mejorar el rendimiento, puede usar la Biblioteca de tareas paralelas (TPL). Aquí hay una buena muestra: cálculo del tamaño del archivo del directorio : ¿cómo hacerlo más rápido?

No lo probé, pero el autor dice que es 3 veces más rápido que un método no multiproceso …

La forma más rápida en que surgió es usando EnumerateFiles con SearchOption.AllDirectories. Este método también permite actualizar la UI mientras revisa los archivos y cuenta el tamaño. Los nombres de rutas largas no causan ningún problema ya que no se intenta crear FileInfo o DirectoryInfo para el nombre de ruta larga. Al enumerar archivos aunque el nombre de archivo es largo, FileInfo devuelto por EnumerateFiles no causa problemas siempre que el nombre del directorio de inicio no sea demasiado largo. Todavía hay un problema con Acceso no autorizado.

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