¿Cómo puedo calcular el tamaño de una carpeta?

Estoy creando una carpeta para almacenar imágenes en caché dentro de Documentos con mi aplicación de iPhone. Quiero poder mantener el tamaño de esta carpeta en 1 MB, por lo que debo verificar el tamaño en bytes de mi carpeta.

Tengo un código para calcular el tamaño del archivo , pero necesito el tamaño de la carpeta.

Cuál sería la mejor forma de hacer esto?

    tl; dr

    Todas las otras respuestas están apagadas 🙂

    Problema

    Me gustaría agregar mis dos centavos a esta vieja pregunta, ya que parece que hay muchas respuestas que son muy similares pero que arrojan resultados que en algunos casos son muy imprecisos.

    Para entender por qué primero tenemos que definir cuál es el tamaño de una carpeta . En mi entender (y probablemente el de OP) es la cantidad de bytes que el directorio incluye todos sus contenidos utiliza en el volumen. O, dicho de otra manera:

    Es el espacio disponible si el directorio se eliminará por completo.

    Soy consciente de que esta definición no es la única forma válida de interpretar la pregunta, pero sí creo que es a lo que la mayoría de los casos de uso se reducen.

    Error

    Todas las respuestas existentes tienen un enfoque muy simple: recorrer el contenido del directorio, sumr los tamaños de los archivos (regulares). Esto no requiere un par de sutilezas.

    • El espacio utilizado en los incrementos de volumen en bloques , no en bytes. Incluso un archivo de un byte usa al menos un bloque.
    • Los archivos transportan metadatos (como cualquier cantidad de atributos extendidos). Esta información debe ir a alguna parte.
    • HFS implementa la compresión del sistema de archivos para almacenar realmente el archivo utilizando menos bytes que su longitud real.

    Solución

    Todas estas razones hacen que las respuestas existentes produzcan resultados poco precisos. Así que propongo esta extensión en NSFileManager (código en github debido a la longitud: Swift 4 , Objective C ) para solucionar el problema. También es bastante más rápido, especialmente con directorios que contienen muchos archivos.

    El núcleo de la solución es utilizar las NSURL de NSURLTotalFileAllocatedSizeKey o NSURLFileAllocatedSizeKey para recuperar tamaños de archivo.

    Prueba

    También configuré un proyecto de prueba de iOS simple , que demuestra las diferencias entre las soluciones. Muestra cuán completamente incorrectos pueden ser los resultados en algunos escenarios.

    En la prueba creo un directorio que contiene 100 archivos pequeños (que van de 0 a 800 bytes). El método folderSize: copiado de alguna otra respuesta calcula un total de 21 kB mientras que el método assignedSize arroja 401 kB.

    Prueba

    Me aseguré de que los resultados de allocatedSize estén más cerca del valor correcto calculando la diferencia de los bytes disponibles en el volumen antes y después de eliminar el directorio de prueba. En mis pruebas, la diferencia siempre fue exactamente igual al resultado de allocatedSize .

    Por favor, vea el comentario de Rob Napier para entender que todavía hay margen de mejora.

    Actuación

    Pero hay otra ventaja: cuando calculo el tamaño de un directorio con 1000 archivos, en mi iPhone 6, el método folderSize: tarda unos 250 ms, mientras que folderSize: atraviesa la misma jerarquía en 35 ms.

    Esto probablemente se deba al uso del nuevo (ish) enumeratorAtURL:includingPropertiesForKeys:options:errorHandler: API para atravesar la jerarquía. Este método le permite especificar propiedades captadas previamente para los elementos que se iterarán, lo que resulta en menos io.

    Resultados

     Test `folderSize` (100 test files) size: 21 KB (21.368 bytes) time: 0.055 s actual bytes: 401 KB (401.408 bytes) Test `allocatedSize` (100 test files) size: 401 KB (401.408 bytes) time: 0.048 s actual bytes: 401 KB (401.408 bytes) Test `folderSize` (1000 test files) size: 2 MB (2.013.068 bytes) time: 0.263 s actual bytes: 4,1 MB (4.087.808 bytes) Test `allocatedSize` (1000 test files) size: 4,1 MB (4.087.808 bytes) time: 0.034 s actual bytes: 4,1 MB (4.087.808 bytes) 

    Saludos por eso Alex, ayudaste mucho, ahora escribiste la siguiente función que hace el truco …

     - (unsigned long long int)folderSize:(NSString *)folderPath { NSArray *filesArray = [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:folderPath error:nil]; NSEnumerator *filesEnumerator = [filesArray objectEnumerator]; NSString *fileName; unsigned long long int fileSize = 0; while (fileName = [filesEnumerator nextObject]) { NSDictionary *fileDictionary = [[NSFileManager defaultManager] fileAttributesAtPath:[folderPath stringByAppendingPathComponent:fileName] traverseLink:YES]; fileSize += [fileDictionary fileSize]; } return fileSize; } 

    Se está obteniendo el número exacto de bytes como lo hace Finder.

    Como un aparte, Finder devuelve dos números. Uno es el tamaño en el disco y el otro es el número real de bytes.

    Por ejemplo, cuando ejecuto este código en una de mis carpetas, aparece en el código con un ‘tamaño de archivo’ de 130398. Cuando ingreso el Finder, dice que el tamaño es de 201 KB en el disco (130,398 bytes).

    Estoy un poco inseguro de qué ir aquí (201 KB o 130.398 bytes) como el tamaño real. Por ahora, iré por el lado seguro y reduciré mi límite a la mitad hasta que descubra qué significa exactamente …

    Si alguien puede agregar más información a estos números diferentes, lo agradecería.

    Aclamaciones,

    En iOS 5, el método -filesAttributesAtPath: está en desuso. Aquí está la versión del primer código publicado con el nuevo método:

     - (unsigned long long int)folderSize:(NSString *)folderPath { NSArray *filesArray = [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:folderPath error:nil]; NSEnumerator *filesEnumerator = [filesArray objectEnumerator]; NSString *fileName; unsigned long long int fileSize = 0; while (fileName = [filesEnumerator nextObject]) { NSDictionary *fileDictionary = [[NSFileManager defaultManager] attributesOfItemAtPath:[folderPath stringByAppendingPathComponent:fileName] error:nil]; fileSize += [fileDictionary fileSize]; } return fileSize; } 

    Esta es la forma de obtener el size carpeta y archivo en MB , KB y GB

    1. Tamaño de la carpeta –

     -(NSString *)sizeOfFolder:(NSString *)folderPath { NSArray *contents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:folderPath error:nil]; NSEnumerator *contentsEnumurator = [contents objectEnumerator]; NSString *file; unsigned long long int folderSize = 0; while (file = [contentsEnumurator nextObject]) { NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[folderPath stringByAppendingPathComponent:file] error:nil]; folderSize += [[fileAttributes objectForKey:NSFileSize] intValue]; } //This line will give you formatted size from bytes .... NSString *folderSizeStr = [NSByteCountFormatter stringFromByteCount:folderSize countStyle:NSByteCountFormatterCountStyleFile]; return folderSizeStr; } 

    Nota: En el caso de las subcarpetas, utilice subpathsOfDirectoryAtPath: lugar de contentsOfDirectoryAtPath:

    2. Tamaño del archivo –

     -(NSString *)sizeOfFile:(NSString *)filePath { NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil]; NSInteger fileSize = [[fileAttributes objectForKey:NSFileSize] integerValue]; NSString *fileSizeStr = [NSByteCountFormatter stringFromByteCount:fileSize countStyle:NSByteCountFormatterCountStyleFile]; return fileSizeStr; } 

    ———- Swift 4.0 ———-

    1. Tamaño de la carpeta –

     func sizeOfFolder(_ folderPath: String) -> String? { do { let contents = try FileManager.default.contentsOfDirectory(atPath: folderPath) var folderSize: Int64 = 0 for content in contents { do { let fullContentPath = folderPath + "/" + content let fileAttributes = try FileManager.default.attributesOfItem(atPath: fullContentPath) folderSize += fileAttributes[FileAttributeKey.size] as? Int64 ?? 0 } catch _ { continue } } /// This line will give you formatted size from bytes .... let fileSizeStr = ByteCountFormatter.string(fromByteCount: folderSize, countStyle: ByteCountFormatter.CountStyle.file) return fileSizeStr } catch let error { print(error.localizedDescription) return nil } } 

    2. Tamaño del archivo –

     func sizeOfFile(_ filePath: String) -> String { do { let fileAttributes = try FileManager.default.attributesOfItem(atPath: filePath) let folderSize = fileAttributes[FileAttributeKey.size] as? Int64 ?? 0 let fileSizeStr = ByteCountFormatter.string(fromByteCount: folderSize, countStyle: ByteCountFormatter.CountStyle.file) return fileSizeStr } catch _ { return nil } } 

    Algo como lo siguiente debería ayudarlo a comenzar. Sin embargo, _documentsDirectory modificar _documentsDirectory en su carpeta específica:

     - (unsigned long long int) documentsFolderSize { NSFileManager *_manager = [NSFileManager defaultManager]; NSArray *_documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *_documentsDirectory = [_documentPaths objectAtIndex:0]; NSArray *_documentsFileList; NSEnumerator *_documentsEnumerator; NSString *_documentFilePath; unsigned long long int _documentsFolderSize = 0; _documentsFileList = [_manager subpathsAtPath:_documentsDirectory]; _documentsEnumerator = [_documentsFileList objectEnumerator]; while (_documentFilePath = [_documentsEnumerator nextObject]) { NSDictionary *_documentFileAttributes = [_manager fileAttributesAtPath:[_documentsDirectory stringByAppendingPathComponent:_documentFilePath] traverseLink:YES]; _documentsFolderSize += [_documentFileAttributes fileSize]; } return _documentsFolderSize; } 

    Usé este código para obtener el tamaño de directorio de 2 directorios, si no existiera un directorio, mostraría cero KB. De lo contrario, la segunda mitad del código mostrará el tamaño de la carpeta junto con KB, MB, GB, respectivamente, y también lo mostrará en un formato limpio: 10.02 MB .

    Pruebe esto de esta manera:

     - (unsigned long long int)folderSize:(NSString *)folderPath { NSArray *filesArray = [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:folderPath error:nil]; NSEnumerator *filesEnumerator = [filesArray objectEnumerator]; NSString *fileName; unsigned long long int fileSize = 0; while (fileName = [filesEnumerator nextObject]) { NSDictionary *fileDictionary = [[NSFileManager defaultManager] fileAttributesAtPath:[folderPath stringByAppendingPathComponent:fileName] traverseLink:YES]; fileSize += [fileDictionary fileSize]; } return fileSize; } -(NSString *)getMPSize { NSString*sizeTypeW = @"bytes"; int app = [self folderSize:@"/PathToTheFolderYouWantTheSizeOf/"]; NSFileManager *manager = [NSFileManager defaultManager]; if([manager fileExistsAtPath:@"/AnotherFolder/"] == YES){ int working = [self folderSize:@"/AnotherFolder/"]; if(working<1){ return @"Size: Zero KB"; }else{ if (working > 1024) { //Kilobytes working = working / 1024; sizeTypeW = @" KB"; } if (working > 1024) { //Megabytes working = working / 1024; sizeTypeW = @" MB"; } if (working > 1024) { //Gigabytes working = working / 1024; sizeTypeW = @" GB"; } return [NSString stringWithFormat:@"App: %i MB, Working: %i %@ ",app/1024/1024, working,sizeTypeW]; } }else{ return [NSString stringWithFormat:@"App: %i MB, Working: Zero KB",app/1024/1024]; } [manager release]; } 

    Aquí hay una respuesta rápida de 2.1 / 2.2 que usa extensiones y se basa en la respuesta de Rok:

     extension NSFileManager { func fileSizeAtPath(path: String) -> Int64 { do { let fileAttributes = try attributesOfItemAtPath(path) let fileSizeNumber = fileAttributes[NSFileSize] let fileSize = fileSizeNumber?.longLongValue return fileSize! } catch { print("error reading filesize, NSFileManager extension fileSizeAtPath") return 0 } } func folderSizeAtPath(path: String) -> Int64 { var size : Int64 = 0 do { let files = try subpathsOfDirectoryAtPath(path) for i in 0 ..< files.count { size += fileSizeAtPath((path as NSString).stringByAppendingPathComponent(files[i]) as String) } } catch { print("error reading directory, NSFileManager extension folderSizeAtPath") } return size } func format(size: Int64) -> String { let folderSizeStr = NSByteCountFormatter.stringFromByteCount(size, countStyle: NSByteCountFormatterCountStyle.File) return folderSizeStr } } 

    Ejemplo de uso:

     let fileManager = NSFileManager.defaultManager() let documentsDirPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] let dirSize: String = fileManager.format(fileManager.folderSizeAtPath(documentsDirPath)) 

    Método actualizado usando el bloque de enumeración

    Calcule el tamaño de la carpeta con solo archivos

     - (NSString *)sizeOfFolder:(NSString *)folderPath { NSArray *folderContents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:folderPath error:nil]; __block unsigned long long int folderSize = 0; [folderContents enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[folderPath stringByAppendingPathComponent:obj] error:nil]; folderSize += [[fileAttributes objectForKey:NSFileSize] intValue]; }]; NSString *folderSizeStr = [NSByteCountFormatter stringFromByteCount:folderSize countStyle:NSByteCountFormatterCountStyleFile]; return folderSizeStr; } 

    Calcule el tamaño de la carpeta con otros directorios secundarios en la carpeta

      NSArray *folderContents = [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:folderPath error:nil]; 

    Obtener tamaño de archivo

     - (NSString *)sizeOfFile:(NSString *)filePath { NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil]; NSInteger fileSize = [[fileAttributes objectForKey:NSFileSize] integerValue]; NSString *fileSizeString = [NSByteCountFormatter stringFromByteCount:fileSize countStyle:NSByteCountFormatterCountStyleFile]; return fileSizeString; } 

    Aquí está el equivalente de Swift 3 de una extensión FileManager basada en la extensión @vitalii:

     extension FileManager { func fileSizeAtPath(path: String) -> Int64 { do { let fileAttributes = try attributesOfItem(atPath: path) let fileSizeNumber = fileAttributes[FileAttributeKey.size] as? NSNumber let fileSize = fileSizeNumber?.int64Value return fileSize! } catch { print("error reading filesize, NSFileManager extension fileSizeAtPath") return 0 } } func folderSizeAtPath(path: String) -> Int64 { var size : Int64 = 0 do { let files = try subpathsOfDirectory(atPath: path) for i in 0 ..< files.count { size += fileSizeAtPath(path:path.appending("/"+files[i])) } } catch { print("error reading directory, NSFileManager extension folderSizeAtPath") } return size } func format(size: Int64) -> String { let folderSizeStr = ByteCountFormatter.string(fromByteCount: size, countStyle: ByteCountFormatter.CountStyle.file) return folderSizeStr }} 

    Creo que usar el método Unix C es mejor para el rendimiento.

     + (long long) folderSizeAtPath: (const char*)folderPath { long long folderSize = 0; DIR* dir = opendir(folderPath); if (dir == NULL) return 0; struct dirent* child; while ((child = readdir(dir))!=NULL) { if (child->d_type == DT_DIR && child->d_name[0] == '.' && (child->d_name[1] == 0 // ignore . || (child->d_name[1] == '.' && child->d_name[2] == 0) // ignore dir .. )) continue; int folderPathLength = strlen(folderPath); char childPath[1024]; // child stpcpy(childPath, folderPath); if (folderPath[folderPathLength-1] != '/'){ childPath[folderPathLength] = '/'; folderPathLength++; } stpcpy(childPath+folderPathLength, child->d_name); childPath[folderPathLength + child->d_namlen] = 0; if (child->d_type == DT_DIR){ // directory folderSize += [self _folderSizeAtPath:childPath]; // // add folder size struct stat st; if (lstat(childPath, &st) == 0) folderSize += st.st_size; } else if (child->d_type == DT_REG || child->d_type == DT_LNK){ // file or link struct stat st; if (lstat(childPath, &st) == 0) folderSize += st.st_size; } } return folderSize; } 

    si queremos obtener el tamaño de cualquier archivo, aquí hay un método, donde solo necesitamos pasar la ruta de ese archivo.

     - (unsigned long long int) fileSizeAt:(NSString *)path { NSFileManager *_manager = [NSFileManager defaultManager]; return [[_manager fileAttributesAtPath:path traverseLink:YES] fileSize]; } 

    Limpié un poco la implementación de la primera respuesta antes de usarla, por lo que ya no arroja advertencias obsoletas + usando una enumeración rápida.

     /** * Calculates the size of a folder. * * @param folderPath The path of the folder * * @return folder size in bytes */ - (unsigned long long int)folderSize:(NSString *)folderPath { NSFileManager *fm = [NSFileManager defaultManager]; NSArray *filesArray = [fm subpathsOfDirectoryAtPath:folderPath error:nil]; unsigned long long int fileSize = 0; NSError *error; for(NSString *fileName in filesArray) { error = nil; NSDictionary *fileDictionary = [fm attributesOfItemAtPath:[folderPath stringByAppendingPathComponent:fileName] error:&error]; if (!error) { fileSize += [fileDictionary fileSize]; }else{ NSLog(@"ERROR: %@", error); } } return fileSize; } 

    Implementación Swift

     class func folderSize(folderPath:String) -> UInt{ // @see http://stackoverflow.com/questions/2188469/calculate-the-size-of-a-folder let filesArray:[String] = NSFileManager.defaultManager().subpathsOfDirectoryAtPath(folderPath, error: nil)! as [String] var fileSize:UInt = 0 for fileName in filesArray{ let filePath = folderPath.stringByAppendingPathComponent(fileName) let fileDictionary:NSDictionary = NSFileManager.defaultManager().attributesOfItemAtPath(filePath, error: nil)! fileSize += UInt(fileDictionary.fileSize()) } return fileSize } 

    No estoy seguro si esto ayuda a alguien, pero quería relatar algunos de mis hallazgos (algunos inspirados en el comentario de @ zneak anterior).

    1. No pude encontrar atajos usando NSDirectoryEnumerator para evitar enumerar a través de archivos para obtener el tamaño total de un directorio.

    2. Para mis pruebas, el uso de -[NSFileManager subpathsOfDirectoryAtPath:path error:nil] fue más rápido que el uso de -[NSFileManager enumeratorAtPath:path] . Esto me parece que podría ser una compensación clásica de tiempo / espacio, ya que subPaths... crea un NSArray en el que luego itera, donde el enumerator... puede que no.

    Algunos antecedentes en el # 1. Asumiendo:

     NSFileManager *fileMan = [NSFileManager defaultManager]; NSString *dirPath = @"/"; // references some directory 

    Entonces

     [fileMan enumeratorAtPath:dirPath] fileAttributes] 

    devuelve nil . El acceso correcto del atributo es directoryAttributes , pero

     [fileMan enumeratorAtPath:dirPath] directoryAttributes] fileSize] 

    devuelve el tamaño de la información del directorio, no la sum recursiva de los tamaños de todos los archivos contenidos (a lá ⌘-I en Finder).

    Creé una extensión simple NSFileManager:

     extension NSFileManager { func fileSizeAtPath(path: String) -> Int { return attributesOfItemAtPath(path, error: nil)?[NSFileSize] as? Int ?? 0 } func folderSizeAtPath(path: String) -> Int { var size = 0 for file in subpathsOfDirectoryAtPath(path, error: nil) as? [String] ?? [] { size += fileSizeAtPath(path.stringByAppendingPathComponent(file)) } return size } } 

    Puedes obtener el tamaño del archivo:

     NSFileManager.defaultManager().fileSizeAtPath("file path") 

    y el tamaño de la carpeta:

     NSFileManager.defaultManager().folderSizeAtPath("folder path")