web-dev-qa-db-fra.com

Comment calculer la taille d'un dossier?

Je crée un dossier pour mettre en cache les images dans Documents avec mon application iPhone. Je veux pouvoir réduire la taille de ce dossier à 1 Mo. Je dois donc vérifier la taille en octets de mon dossier.

J'ai du code pour calculer la taille du fichier , mais j'ai besoin de la taille du dossier.

Quelle serait la meilleure façon de faire cela?

65
joseph_carney

tl; dr

Toutes les autres réponses sont désactivées :)

Problème

Je voudrais ajouter mes deux cents à cette vieille question car il semble y avoir beaucoup de réponses qui sont toutes très similaires mais donnent des résultats qui sont dans certains cas très imprécis.

Pour comprendre pourquoi nous devons d'abord définir quelle est la taille d'un dossier. À ma connaissance (et probablement celle de l'OP), c'est la quantité d'octets que le répertoire, y compris tout son contenu, utilise sur le volume. Ou, autrement dit:

C'est l'espace qui devient disponible si le répertoire est complètement supprimé.

Je suis conscient que cette définition n'est pas le seul moyen valable d'interpréter la question, mais je pense que c'est à cela que se résument la plupart des cas d'utilisation.

Erreur

Les réponses existantes adoptent toutes une approche très simple: parcourir le contenu du répertoire, en additionnant la taille des fichiers (normaux). Cela ne prend pas en compte quelques subtilités.

  • L'espace utilisé sur les incréments de volume dans les blocs , pas en octets. Même un fichier d'un octet utilise au moins un bloc.
  • Les fichiers transportent des métadonnées (comme n'importe quel nombre d'attributs étendus). Ces données doivent aller quelque part.
  • HFS déploie la compression du système de fichiers pour stocker réellement le fichier en utilisant moins d'octets que sa longueur réelle.

Solution

Toutes ces raisons font que les réponses existantes produisent des résultats imprécis. Je propose donc cette extension sur NSFileManager (code sur github en raison de la longueur: Swift 4 , Objective C ) pour remédier au problème. C'est aussi un peu plus rapide, surtout avec les répertoires contenant beaucoup de fichiers.

Le cœur de la solution consiste à utiliser les propriétés NSURLNSURLTotalFileAllocatedSizeKey ou NSURLFileAllocatedSizeKey pour récupérer la taille des fichiers.

Tester

J'ai également mis en place n simple projet de test iOS , démontrant les différences entre les solutions. Il montre à quel point les résultats peuvent être totalement erronés dans certains scénarios.

Dans le test, je crée un répertoire contenant 100 petits fichiers (allant de 0 à 800 octets). La méthode folderSize: Copiée à partir d'une autre réponse calcule un total de 21 Ko alors que ma méthode allocatedSize donne 401 Ko.

Preuve

Je me suis assuré que les résultats de allocatedSize sont plus proches de la valeur correcte en calculant la différence des octets disponibles sur le volume avant et après la suppression du répertoire de test. Dans mes tests, la différence était toujours exactement égale au résultat de allocatedSize.

Veuillez lire le commentaire de Rob Napier pour comprendre qu'il y a encore place à amélioration.

Performance

Mais il y a un autre avantage: lors du calcul de la taille d'un répertoire de 1000 fichiers, sur mon iPhone 6, la méthode folderSize: Prend environ 250 ms tandis que allocatedSize parcourt la même hiérarchie en 35 ms.

Cela est probablement dû à l'utilisation de la nouvelle API (ish) enumeratorAtURL:includingPropertiesForKeys:options:errorHandler: De NSFileManager pour parcourir la hiérarchie. Cette méthode vous permet de spécifier des propriétés prérécupérées pour les éléments à itérer, ce qui entraîne moins d'io.

Résultats

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)
65
Nikolai Ruhe

Bravo pour cet Alex, vous avez beaucoup aidé, avez maintenant écrit la fonction suivante qui fait l'affaire ...

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

Il arrive avec le nombre exact d'octets comme le fait le Finder.

En passant, Finder renvoie deux nombres. L'un est la taille du disque et l'autre le nombre réel d'octets.

Par exemple, lorsque j'exécute ce code sur l'un de mes dossiers, il revient dans le code avec une "taille de fichier" de 130398. Lorsque j'archive le Finder, il indique que la taille est de 201 Ko sur le disque (130 398 octets).

Je ne sais pas trop quoi choisir ici (201 Ko ou 130 398 octets) comme taille réelle. Pour l'instant, je vais aller du bon côté et réduire ma limite de moitié jusqu'à ce que je sache ce que cela signifie exactement ...

Si quelqu'un peut ajouter plus d'informations à ces différents chiffres, je l'apprécierais.

À votre santé,

42
joseph_carney

Dans iOS 5, la méthode -filesAttributesAtPath: est obsolète. Voici la version du premier code posté avec la nouvelle méthode:

- (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;
}
30
ndoc

Voici comment obtenir le dossier et le fichier size dans [~ # ~] mb [~ # ~] = , KO et GB ---

1. Taille du dossier -

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

Remarque: Dans le cas de sous-dossiers, veuillez utiliser subpathsOfDirectoryAtPath: au lieu de contentsOfDirectoryAtPath:

2. Taille du fichier -

-(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. Taille du dossier -

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. Taille du fichier -

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
    }
}
30
TheTiger

Quelque chose comme ce qui suit devrait vous aider à démarrer. Vous devrez modifier _documentsDirectory dans votre dossier spécifique, cependant:

- (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;
}
10
Alex Reynolds

J'ai utilisé ce code pour obtenir la taille du répertoire de 2 répertoires, si un répertoire n'existait pas, il afficherait zéro Ko. Sinon, la seconde moitié du code affichera la taille du dossier avec les Ko, Mo, Go, respectivement, et l'affichera également dans un format propre: 10.02 MB.

Essayez ceci quelque chose comme ceci:

- (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];
}
4
WrightsCS

Voici une réponse Swift 2.1/2.2 en utilisant des extensions et en s'appuyant sur la réponse 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
    }

}

Exemple d'utilisation:

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

Voici l'équivalent Swift 3 d'une extension FileManager basée sur l'extension @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
}}
3
Larry Mickie

Méthode mise à jour utilisant un bloc d'énumération

Calculer la taille du dossier avec uniquement des fichiers

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

Calculer la taille du dossier avec les autres sous-répertoires du dossier

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

Obtenir la taille du fichier

- (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;
}
3
icodebuster

Je pense que l'utilisation de la méthode Unix C est meilleure pour les performances.

+ (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;
}
2
ZYiOS

Mise en œuvre rapide

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
}
1
Luyu Zhang

J'ai nettoyé un peu l'implémentation de la première réponse avant de l'utiliser, donc elle ne lance plus d'avertissements obsolètes + en utilisant une énumération rapide.

/**
 *  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;
}
1
Stoto

si nous voulons obtenir la taille d'un fichier, voici une méthode, où nous n'avons besoin que de passer le chemin de ce fichier.

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

Je ne sais pas si cela aide quelqu'un, mais je voulais raconter certaines de mes conclusions (certaines inspirées par le commentaire de @ zneak ci-dessus).

  1. Je n'ai trouvé aucun raccourci en utilisant NSDirectoryEnumerator pour éviter d'énumérer les fichiers afin d'obtenir la taille totale contenue d'un répertoire.

  2. Pour mes tests, en utilisant -[NSFileManager subpathsOfDirectoryAtPath:path error:nil] était plus rapide que l'utilisation de -[NSFileManager enumeratorAtPath:path]. Cela me semble être un compromis temps/espace classique, comme subPaths... crée un NSArray sur lequel il itère ensuite, où enumerator... peut-être pas.

Quelques informations sur # 1. En supposant:

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

Alors

[fileMan enumeratorAtPath:dirPath] fileAttributes]

renvoie nil. L'accesseur d'attribut correct est directoryAttributes, mais

[fileMan enumeratorAtPath:dirPath] directoryAttributes] fileSize]

renvoie la taille des informations du répertoire, et non la somme récursive des tailles de tous les fichiers contenus (un lá ⌘-I dans le Finder).

0
Clay Bridges

J'ai créé une simple extension 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
  }
}

Vous pouvez obtenir la taille du fichier:

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

et la taille du dossier:

NSFileManager.defaultManager().folderSizeAtPath("folder path")
0
Rok Gregorič