如何计算文件夹的大小?


71

我正在创建一个文件夹,以使用我的iPhone App在“文档”中缓存图像。我希望能够将此文件夹的大小减小到1MB,因此我需要检查文件夹的大小(以字节为单位)。

我有代码来计算文件大小,但是我需要文件夹的大小。

最好的方法是什么?


使用NSDirectoryEnumerator和fileAttributes方法即可。
zneak

不确定您的意思,但是我尝试在下面的答案进行全面探讨。TL; DR版本看起来像没有逃避枚举的文件。
粘土桥

Answers:


81

tl; dr

所有其他答案都关闭了:)

问题

我想在这个老问题上加上两分钱,因为似乎有很多答案都非常相似,但是在某些情况下得出的结果非常不准确。

要了解为什么我们首先必须定义文件夹大小。以我的理解(可能是OP之一),它是目录(包括其所有内容)在卷上使用的字节数。或者,换一种说法:

如果目录将被完全删除,则该空间变为可用。

我知道这个定义不是解释问题的唯一有效方法,但我确实认为这是大多数用例的根源。

错误

现有的答案都采用非常简单的方法:遍历目录内容,增加(常规)文件的大小。这并没有考虑到一些微妙之处。

  • 卷上使用的空间以为单位递增,而不是以字节为单位递增。即使是一个字节的文件也至少使用一个块。
  • 文件包含元数据(如任意数量的扩展属性)。此数据必须放在某处。
  • HFS部署文件系统压缩,以实际使用较少的字节(而不是其实际长度)存储文件。

所有这些原因使现有的答案产生不精确的结果。因此,我提议在此扩展名NSFileManager(由于长度而在github上的代码:Swift 4Objective C)来解决这个问题。它也相当快,特别是对于包含很多文件的目录。

该解决方案的核心是使用NSURLNSURLTotalFileAllocatedSizeKeyNSURLFileAllocatedSizeKey性的判定来检索文件的大小。

测试

我还建立了一个简单的iOS测试项目,演示了解决方案之间的差异。它显示了在某些情况下结果可能是完全错误的。

在测试中,我创建一个包含100个小文件的目录(范围从0到800字节)。folderSize:从其他答案中复制的方法总共计算出21 kB,而我的allocatedSize方法得出401 kB。

证明

allocatedSize通过计算删除测试目录之前和之后卷上可用字节的差异,我确保的结果更接近正确的值。在我的测试中,差异始终等于的结果allocatedSize

请参阅Rob Napier的评论,以了解仍有改进的空间。

性能

但是还有另一个优点:在计算包含1000个文件的目录的大小时,在我的iPhone 6上,此folderSize:方法大约需要250毫秒,而allocatedSize在35毫秒内遍历相同的层次结构。

这可能是由于使用NSFileManager的new(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)

1
非常有用的信息。有一个(非常)细小的皱纹,这很可能与“如果目录将被完全删除的空间变得可用”区分开。如果树外inode中有任何硬链接,则删除这些硬链接实际上不会释放文件。硬链接不是特别常见,在目录中不那么常见,甚至在iOS上也不常见,因此这只是很小的一部分,并且不会因这项技术的实用性而减少。
罗布·纳皮尔

1
@NikolaiRuhe,您好,我正在尝试在Swift项目中使用您的课程。我通过一个Objective-C标头添加了它,我可以像这样调用方法NSFileManager.defaultManager().nr_getAllocatedSize(UnsafeMutablePointer<UInt64>, ofDirectoryAtURL: NSURL!)。但是我不确定应该向size参数传递什么。我浏览了您的演示项目,但仍然有些困惑。
Isuru

2
@Isuru size参数是该方法的实际结果,并由引用返回(在Swift中使用起来有点尴尬)。如果您不想简单地将方法移植到更快的版本,则可以按如下所示通过引用传递变量:var size = UInt64(0); nr_getAllocatedSize(&size​, ofDirectoryAtURL: someURL)
Nikolai Ruhe

1
@NikolaiRuhe哇,这超出了我的期望!非常感谢。
Isuru

3
@Keiwan我特此在MIT许可下发布它。
Nikolai Ruhe

43

为亚历克斯欢呼,您提供了很多帮助,现在已经编写了以下功能,可以发挥作用...

- (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返回两个数字。一个是磁盘上的大小,另一个是实际的字节数。

例如,当我在一个文件夹中运行此代码时,它以130398的“文件大小”返回代码中。当我在Finder中签入时,它说磁盘上的大小为201KB(130,398字节)。

有点不确定此处的实际大小(201KB或130,398字节)。现在,我会安全地将限制减少一半,直到我知道这到底意味着什么...

如果有人可以向这些不同的号码添加更多信息,我将不胜感激。

干杯,


我可以想到两种复杂性来解释这种差异:1024字节等于1 KB(或KiB?取决于您询问的人)以及“块大小”,其中文件的字节可以占用更大磁盘块的倍数。空间—这是文件系统级别的优化,取决于磁盘格式化和磁盘容量。例如,一个1024字节的文件实际上可以占用整个16 KB的块,因此尽管它仅使用1024个字节,却被列为16 KB的文件。
亚历克斯·雷诺兹

@AlexReynolds-正确。“磁盘大小”是指实际用于存储文件的磁盘空间。第二个数字是文件本身的大小。这是两件事,磁盘上的大小几乎总是会更大。
DougW

1
@iphone_developer-值得一提的是,此方法非常昂贵。如果在主线程上执行此操作,则在包含成百上千个小文件的大文件夹中调用它会使应用程序停止运行。如果需要的话,这没什么不对,值得指出。
DougW

有什么办法以编程方式在磁盘上找到大小?
tumbudu 2015年

Nikolai Ruhe的答案是处理大量文件的方法。
凯尔(Kyle)

36

这是如何获得文件夹和文件sizeMBKBGB ---

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 {
        print(error)
    }
    return nil
}

30

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

5
当然,您也可以使用快速枚举:for(NSString * filesArray中的fileName){}
MBulli

11

类似以下内容应有助于您入门。不过,您需要修改_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;
}

fileAttributesAtPath已弃用
user1105951

4

我使用此代码来获取2个目录的目录大小,如果一个目录不存在,它将显示零KB。否则,代码的后半部分将分别显示文件夹大小以及KB,MB和GB,并且还将以干净的格式显示它: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];
}

4

这是一个使用扩展的快速2.1 / 2.2答案,并以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
    }

}

用法示例:

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

3

使用枚举块更新了方法

仅使用文件计算文件夹大小

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

3

这是基于@vitalii扩展名的FileManager扩展的Swift 3等效项:

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

2

我认为使用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;
}

1

如果我们想获取任何文件的大小,则这里是一个方法,我们只需要传递该文件的路径即可。

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

根据您的建议,您将只处理目录占位符的大小。如果要计算包含所有内容的目录大小,则必须通过循环操作获取目录内的每个文件大小,然后一个一个地添加。像上面的例子。而且,很久以前不推荐使用fileAttributesAtPath方法。
ytur

不推荐使用
fileAttributesAtPath

1

我在使用它之前先清理了第一个答案的实现,因此它不再抛出弃用的警告+使用快速枚举。

/**
 *  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

快速实施

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
}

0

不确定这是否对任何人有帮助,但是我想介绍一下我的一些发现(一些受上述@zneak的评论启发)。

  1. 我找不到NSDirectoryEnumerator用于避免枚举文件以获取目录总包含大小的快捷方式。

  2. 在我的测试中,使用-[NSFileManager subpathsOfDirectoryAtPath:path error:nil]比使用更快-[NSFileManager enumeratorAtPath:path]。在我看来,这可能是经典的时间/空间折衷方案,因为它subPaths...创建了一个NSArray,然后在其上进行迭代,而在其他地方则enumerator...可能不会。

#1的一些背景。假设:

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

然后

[fileMan enumeratorAtPath:dirPath] fileAttributes]

返回nil。正确的属性访问器是directoryAttributes,但是

[fileMan enumeratorAtPath:dirPath] directoryAttributes] fileSize]

返回目录信息的大小,而不是所有包含的文件(Finder中的láI-I)大小的递归和。


0

我创建了一个简单的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
  }
}

您可以获取文件大小:

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

和文件夹大小:

NSFileManager.defaultManager().folderSizeAtPath("folder path")
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.