Как я могу рассчитать размер папки? - PullRequest
64 голосов
/ 03 февраля 2010

Я создаю папку для кэширования изображений внутри документов с помощью приложения для iPhone. Я хочу иметь возможность сохранить размер этой папки до 1 МБ, поэтому мне нужно проверить размер в байтах моей папки.

У меня есть код для расчета размера файла , но мне нужен размер папки.

Как лучше всего это сделать?

Ответы [ 15 ]

60 голосов
/ 22 февраля 2015

ТЛ; др

Все остальные ответы отключены:)

Проблема

Я бы хотел добавить свои два цента к этому старому вопросу, так как, кажется, есть много ответов, которые очень похожи, но дают результаты, которые в некоторых случаях очень неточны.

Чтобы понять, почему мы сначала должны определить, каков размер папки . В моем понимании (и, вероятно, в одном из OP) это количество байтов, которое каталог, включая все его содержимое, использует на томе. Или, по-другому:

Это пространство становится доступным, если каталог будет полностью удален.

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

Error

Все существующие ответы используют очень простой подход: просмотрите содержимое каталога, сложив размеры (обычных) файлов. Это не учитывает пару тонкостей.

  • Пространство, используемое для увеличения тома в блоках , а не в байтах. Даже однобайтовый файл использует хотя бы один блок.
  • Файлы содержат метаданные (как любое количество расширенных атрибутов). Эти данные должны куда-то идти.
  • HFS развертывает файловую систему сжатие , чтобы фактически сохранить файл, используя меньше байтов, чем его реальная длина.

Решение

Все эти причины заставляют существующие ответы давать неточные результаты. Поэтому я предлагаю это расширение на NSFileManager (код на github из-за длины: Swift 4 , Objective C ) для устранения проблемы. Это также немного быстрее, особенно с каталогами, содержащими много файлов.

Суть решения заключается в использовании NSURL * NSURLTotalFileAllocatedSizeKey или NSURLFileAllocatedSizeKey свойств для получения размеров файлов.

Тест

Я также создал простой тестовый проект iOS , демонстрирующий различия между решениями. Он показывает, насколько ошибочными могут быть результаты в некоторых сценариях.

В тесте я создаю каталог, содержащий 100 маленьких файлов (от 0 до 800 байт). Метод folderSize:, скопированный из какого-либо другого ответа, вычисляет в общей сложности 21 кБ, в то время как мой метод allocatedSize дает 401 кБ.

Доказательство

Я удостоверился, что результаты allocatedSize ближе к правильному значению, вычислив разницу доступных байтов в томе до и после удаления тестового каталога. В моих тестах разница всегда была точно равна результату allocatedSize.

Пожалуйста, ознакомьтесь с комментарием Роба Напира, чтобы понять, что еще есть возможности для улучшения.

Performance

Но есть и другое преимущество: при расчете размера каталога с 1000 файлами на моем iPhone 6 метод folderSize: занимает около 250 мс, а allocatedSize проходит по той же иерархии за 35 мс.

Это, вероятно, связано с использованием NSFileManager нового (ish) enumeratorAtURL:includingPropertiesForKeys:options:errorHandler: API для обхода иерархии. Этот метод позволяет вам указать предварительно выбранные свойства для элементов, которые будут повторяться, в результате чего меньше io.

Результаты

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)
42 голосов
/ 03 февраля 2010

Приветствую вас, Алекс, вы очень помогли, теперь написали следующую функцию, которая делает трюк ...

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

Он рассчитывает точное количество байтов, как это делает Finder.

Кроме того, Finder возвращает два числа.Один - это размер на диске, а другой - фактическое количество байтов.

Например, когда я запускаю этот код в одной из моих папок, он возвращается в коде с «fileSize», равным 130398. Когда я проверяю в Finder, он говорит, что размер на диске 201 КБ (130 398)байт).

Я немного не уверен в том, что использовать (201 КБ или 130 398 байт) в качестве фактического размера.Сейчас я перейду на безопасную сторону и урежу свой лимит вдвое, пока не выясню, что это значит точно ...

Если кто-нибудь сможет добавить больше информации к этим различным цифрам, я был бы признателен.

Приветствия,

30 голосов
/ 22 апреля 2013

Это способ получения папки и файла size в МБ , КБ и ГБ ---

1. Размер папки -

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

Примечание: В случае подпапок, пожалуйста, используйте subpathsOfDirectoryAtPath: вместо contentsOfDirectoryAtPath:

2. Размер файла -

-(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. Размер папки -

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. Размер файла -

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 голосов
/ 28 июня 2012

В iOS 5 метод -filesAttributesAtPath: устарел. Вот версия первого кода, опубликованного с новым методом:

- (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;
}
10 голосов
/ 03 февраля 2010

Что-то вроде следующего должно помочь вам начать. Вам нужно изменить _documentsDirectory для вашей конкретной папки, хотя:

- (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;
}
4 голосов
/ 11 ноября 2015

Вот быстрый ответ 2.1 / 2.2 с использованием расширений и построения ответа Рока:

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
    }

}

Пример использования:

let fileManager = NSFileManager.defaultManager()
let documentsDirPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0]
let dirSize: String = fileManager.format(fileManager.folderSizeAtPath(documentsDirPath))
4 голосов
/ 15 февраля 2011

Я использовал этот код, чтобы получить размер каталога 2 каталога, если один каталог не существует, он будет отображать ноль КБ. В противном случае вторая половина кода будет отображать размер папки вместе с КБ, МБ, ГБ соответственно и отображать ее в чистом формате: 10.02 MB.

Попробуйте что-то вроде этого:

- (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];
}
3 голосов
/ 10 сентября 2017

Вот эквивалент Swift 3 расширения FileManager, основанного на расширении @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 голосов
/ 01 февраля 2014

Обновлен метод с использованием блока перечисления

Рассчитать размер папки только с файлами

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

Рассчитать размер папки с другими подкаталогами в папке

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

Получить размер файла

- (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;
}
2 голосов
/ 09 сентября 2013

Я думаю, что использование метода Unix C лучше для производительности.

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