How can I calculate the size of a folder?
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 che开发者_Python百科ck 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?
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)
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,
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
}
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;
}
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;
}
I used this code to get the directory size of 2 directories, if one directory didnt exist, it would show Zero KB. Otherwise, the second half of the code will display the folder size along with the KB, MB, GB, respectively, and it will also display it in a clean format: 10.02 MB
.
Try this something like this:
- (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];
}
Here's a swift 2.1/2.2 answer using extensions and building off of Rok's answer:
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
}
}
Usage example:
let fileManager = NSFileManager.defaultManager()
let documentsDirPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0]
let dirSize: String = fileManager.format(fileManager.folderSizeAtPath(documentsDirPath))
Updated Method using enumeration block
Calculate Folder Size with only files
- (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;
}
Calculate Folder Size with other sub directories in the folder
NSArray *folderContents = [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:folderPath error:nil];
Get File Size
- (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;
}
Here is the Swift 3 equivalent of a FileManager extension based off of @vitalii extension:
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
}}
I think use Unix C method is better for performance.
+ (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;
}
if we want to get the size of any file then here is a method, where we only need to pass path of that file.
- (unsigned long long int) fileSizeAt:(NSString *)path {
NSFileManager *_manager = [NSFileManager defaultManager];
return [[_manager fileAttributesAtPath:path traverseLink:YES] fileSize];
}
I cleaned up a bit the first answer's implementation before using it, so it no longer throws deprecated warnings + using fast enumeration.
/**
* 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;
}
Swift Implementation
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
}
Not sure if this helps anyone, but I wanted to relate some of my findings (some inspired by @zneak's comment above).
I could not find any shortcuts using
NSDirectoryEnumerator
to avoid enumerating through files to get the total contained size of a directory.For my tests, using
-[NSFileManager subpathsOfDirectoryAtPath:path error:nil]
was faster than using-[NSFileManager enumeratorAtPath:path]
. This looks to me like it might be a classic time/space tradeoff, assubPaths...
creates an NSArray on which it then iterates, whereenumerator...
might not.
Some background on #1. Assuming:
NSFileManager *fileMan = [NSFileManager defaultManager];
NSString *dirPath = @"/"; // references some directory
Then
[fileMan enumeratorAtPath:dirPath] fileAttributes]
returns nil
. The correct attribute accessor is directoryAttributes
, but
[fileMan enumeratorAtPath:dirPath] directoryAttributes] fileSize]
returns the size of the directory information, not the recursive sum of the sizes of all contained files (a lá ⌘-I in Finder).
I've created a simple NSFileManager extension:
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
}
}
You can get the file size:
NSFileManager.defaultManager().fileSizeAtPath("file path")
and the folder size:
NSFileManager.defaultManager().folderSizeAtPath("folder path")
精彩评论