How can I calculate the size of a folder?

43,235

Solution 1

tl;dr

All the other answers are off :)

Problem

I'd like to add my two cents to this old question as there seem to be many answers that are all very similar but yield results that are in some cases very unprecise.

To understand why we first have to define what the size of a folder is. In my understanding (and probably the one of the OP) it is the amount of bytes that the directory including all of its contents uses on the volume. Or, put in another way:

It is the space becoming available if the directory would be completely removed.

I'm aware that this definition is not the only valid way to interpret the question but I do think it's what most use cases boil down to.

Error

The existing answers all take a very simple approach: Traverse the directory contents, adding up the sizes of (regular) files. This does not take a couple of subtleties into account.

  • The space used on the volume increments in blocks, not in bytes. Even a one byte file uses at least one block.
  • Files carry around meta data (like any number of extended attributes). This data must go somewhere.
  • HFS deploys file system compression to actually store the file using less bytes then its real length.

Solution

All of these reasons make the existing answers produce unprecise results. So I'm proposing this extension on NSFileManager (code on github due to length: Swift 4, Objective C) to remedy the problem. It's also quite a bit faster, especially with directories containing a lot of files.

The core of the solution is to use NSURL's NSURLTotalFileAllocatedSizeKey or NSURLFileAllocatedSizeKey properies to retrieve file sizes.

Test

I've also set up a simple iOS test project, demonstrating the differences between the solutions. It shows how utterly wrong the results can be in some scenarios.

In the test I create a directory containing 100 small files (ranging from 0 to 800 bytes). The folderSize: method copied from some other answer calculates a total of 21 kB while my allocatedSize method yields 401 kB.

Proof

I made sure that the results of allocatedSize are closer to the correct value by calculating the difference of the available bytes on the volume before and after deleting the test directory. In my tests the difference was always exactly equal to the result of allocatedSize.

Please see Rob Napier's comment to understand that there's still room for improvement.

Performance

But there's another advantage: When calculating the size of a directory with 1000 files, on my iPhone 6 the folderSize: method takes about 250 ms while allocatedSize traverses the same hierarchy in 35 ms.

This is probably due to using NSFileManager's new(ish) enumeratorAtURL:includingPropertiesForKeys:options:errorHandler: API to traverse the hierachy. This method let's you specify prefetched properties for the items to be iterated, resulting in less io.

Results

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)

Solution 2

Cheers for that Alex, you helped a lot, have now written the following function which does the trick...

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

It is coming up with the exact number of bytes as Finder does.

As an aside, Finder returns two numbers. One is the size on the disk and the other is the actual number of bytes.

For example, when I run this code on one of my folders, it comes back in the code with a 'fileSize' of 130398. When I check in Finder, it says the size is 201KB on disk (130,398 bytes).

Am a little unsure of what to go with here (201KB or 130,398 bytes) as the actual size. For now, I'll go on the safe side and cut my limit in half until I find out what this means exactly...

If anyone can add any more information to these differing numbers I'd appreciate it.

Cheers,

Solution 3

This is how to get folder and file size in MB, KB and GB ---

1. Folder Size -

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

Note: In case of sub folders please use subpathsOfDirectoryAtPath: instead of contentsOfDirectoryAtPath:

2. File Size -

-(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. Folder Size -

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. File Size -

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
}

Solution 4

In iOS 5 the method -filesAttributesAtPath: is deprecated. Here is the version of the first code posted with the new method:

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

Solution 5

Something like the following should help get you started. You'll need to modify _documentsDirectory to your specific folder, though:

- (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;
}
Share:
43,235
joseph_carney
Author by

joseph_carney

Updated on July 05, 2022

Comments

  • joseph_carney
    joseph_carney almost 2 years

    I'm creating a folder to cache images inside Documents with my iPhone App. I want to be able to keep the size of this folder down to 1MB, so I need to to check the size in bytes of my folder.

    I have code to calculate the size of file, but I need the size of the folder.

    What would be the best way to do this?

  • Alex Reynolds
    Alex Reynolds over 14 years
    There are two complications I can think of to explain the disparity: 1024 bytes is equivalent to 1 KB (or KiB? depends on who you ask) and also "block size", where a file's bytes can take up multiples of larger chunks of disk space — this is a file system level optimization that is dependent on the disk formatting and disk capacity. A 1024 byte file could actually take up a whole 16 KB block, for example, and thus be listed as a 16 KB file although it only uses 1024 bytes.
  • DougW
    DougW over 12 years
    @AlexReynolds - Correct. The "size on disk" means how much disk space is actually used to store the files. The second number is the size of the files themselves. Those are two different things, and the size on disk will almost always be larger.
  • DougW
    DougW over 12 years
    @iphone_developer - One thing worth mentioning is that this method is extremely expensive. Calling it on a large folder with hundreds or thousands of small files will grind your app to a halt if done on the main thread. Nothing wrong with that if you need it, just worth pointing out.
  • MBulli
    MBulli almost 12 years
    You can also use fast enumeration of course: for(NSString *fileName in filesArray) { }
  • ytur
    ytur about 11 years
    with your suggestion, you will only handle the directory placeholder size. if you'd like to calculate the directory size with all of the contents, you must get the each file size inside the directory with some loop operation and add one by one. like above examples. and also, fileAttributesAtPath method deprecated long long time ago.
  • Rob Napier
    Rob Napier about 9 years
    Very useful information. There is one (very) small wrinkle that likely distinguishes this from "the space becoming available if the directory would be completely removed." If there are any hard links from inodes outside the tree, then removing these hard links won't actually free the files. Hard links are not particularly common, less common across directories, and even less common on iOS, so this is a pretty minor amount and doesn't diminish from the usefulness of this technique.
  • Nikolai Ruhe
    Nikolai Ruhe about 9 years
    @RobNapier Thanks, I agree (I added this as a note in the actual source code). There are some other very subtle discrepancies, like meta data on subdirectories (can't be queried with the NSURL API) or space needed for symbolic links and other file system anomalies. But the results are at least an improvement over the simple file size solutions.
  • tumbudu
    tumbudu about 9 years
    is there any way to find size on disk programmatically?
  • Isuru
    Isuru about 8 years
    Hi @NikolaiRuhe, I'm trying to use your class in a Swift project. I added it via a Objective-C header and I can can call the method like this NSFileManager.defaultManager().nr_getAllocatedSize(UnsafeMut‌​ablePointer<UInt64>, ofDirectoryAtURL: NSURL!). However I'm not sure what should I pass in to the size parameter. I looked through your demo project but I'm still a little confused.
  • Nikolai Ruhe
    Nikolai Ruhe about 8 years
    @Isuru The size parameter is the actual result of the method and is returned by reference (which is a little awkward to use in Swift). If you don't want to simply port the method to a swiftier version, you can pass a variable by reference as follows: var size = UInt64(0); nr_getAllocatedSize(&size​, ofDirectoryAtURL: someURL)
  • Isuru
    Isuru about 8 years
    @NikolaiRuhe Oh I see. Thank you!
  • Nikolai Ruhe
    Nikolai Ruhe about 8 years
    @Isuru I just posted a port to Swift. This might feel more natural to use.
  • Isuru
    Isuru about 8 years
    @NikolaiRuhe Wow that's more than I hoped for! Thanks so much.
  • Gary Kenyon
    Gary Kenyon almost 8 years
    This doesn't work entirely in Swift3, there are issues when creating the enumerator.
  • user1105951
    user1105951 over 7 years
    fileAttributesAtPath is deprecated
  • user1105951
    user1105951 over 7 years
    fileAttributesAtPath is deprecated
  • Keiwan
    Keiwan over 6 years
    This is great! Is there any specific license for this code or a restriction on where it can be used? MIT?
  • Nikolai Ruhe
    Nikolai Ruhe over 6 years
    @Keiwan I hereby release it under the MIT license.
  • Kyle
    Kyle about 6 years
    The answer by Nikolai Ruhe is way way quicker for large quantities of files.
  • trndjc
    trndjc over 2 years
    The only caveat I'd add to the Swift solution is the use of UInt64 for bytes. I would recommend against this. Swift already treats bytes as integers and that is because when you need to compute sizes, you want to be able to work with negative numbers. If, for example, you need to compute how much over or under a directory's size is to a byte limit (i.e. var fat = dirSize - byteLimit), you will never get a negative number using unsigned integers and thus would never know how much smaller a directory is than its byte limit. Use Int or Int64 if this matters to you.