【问题标题】:ObjC/Cocoa class for converting size to human-readable string?用于将大小转换为人类可读字符串的 ObjC/Cocoa 类?
【发布时间】:2009-02-21 08:11:29
【问题描述】:

有没有一种简单的方法可以做类似的事情......

[NSMagicDataConverter humanStringWithBytes:20000000]

..哪个会返回“19.1MB”?

【问题讨论】:

标签: objective-c cocoa


【解决方案1】:

从 OS X 10.8 和 iOS 6 开始,您可以使用 NSByteCountFormatter

您的示例如下所示:

[NSByteCountFormatter stringFromByteCount:20000000 countStyle:NSByteCountFormatterCountStyleFile];

【讨论】:

  • 使用它的唯一缺点是格式是强制使用当前语言环境的。
【解决方案2】:

我会把它混入一个 NSFormatter 子类中。

#import <Foundation/Foundation.h>

@interface SOFileSizeFormatter : NSNumberFormatter 
{
    @private
    BOOL useBaseTenUnits;
}

/** Flag signaling whether to calculate file size in binary units (1024) or base ten units (1000).  Default is binary units. */
@property (nonatomic, readwrite, assign, getter=isUsingBaseTenUnits) BOOL useBaseTenUnits;

@end

static const char sUnits[] = { '\0', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y' };
static int sMaxUnits = sizeof sUnits - 1;

@implementation SOFileSizeFormatter

@synthesize useBaseTenUnits;

- (NSString *) stringFromNumber:(NSNumber *)number
{
    int multiplier = useBaseTenUnits ? 1000 : 1024;
    int exponent = 0;

    double bytes = [number doubleValue];

    while ((bytes >= multiplier) && (exponent < sMaxUnits)) {
        bytes /= multiplier;
        exponent++;
    }

    return [NSString stringWithFormat:@"%@ %cB", [super stringFromNumber: [NSNumber numberWithDouble: bytes]], sUnits[exponent]];
}

@end

用法:

NSString *path = ...; // path to a file of 1,500,000 bytes
NSString *sizeString = nil;

NSNumber *sizeAttrib = [[[NSFileManager defaultManager] attributesOfItemAtPath:path error:NULL]objectForKey:NSFileSize];

SOFileSizeFormatter *sizeFormatter = [[[SOFileSizeFormatter alloc] init] autorelease];
[sizeFormatter setMaximumFractionDigits:2];

sizeString = [sizeFormatter stringFromNumber:sizeAttrib];
// sizeString ==> @"1.43 MB"

[sizeFormatter setUseBaseTenUnits:YES];
sizeString = [sizeFormatter stringFromNumber:sizeAttrib];
// sizeString ==> @"1.5 MB"

【讨论】:

  • 我这样做了,但只有两种方法:stringFromSize: && stringFromSpeed:
【解决方案3】:

这是我自己对这个问题的看法:

enum {
    kUnitStringBinaryUnits     = 1 << 0,
    kUnitStringOSNativeUnits   = 1 << 1,
    kUnitStringLocalizedFormat = 1 << 2
};

NSString* unitStringFromBytes(double bytes, uint8_t flags){

    static const char units[] = { '\0', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y' };
    static int maxUnits = sizeof units - 1;

    int multiplier = (flags & kUnitStringOSNativeUnits && !leopardOrGreater() || flags & kUnitStringBinaryUnits) ? 1024 : 1000;
    int exponent = 0;

    while (bytes >= multiplier && exponent < maxUnits) {
        bytes /= multiplier;
        exponent++;
    }
    NSNumberFormatter* formatter = [[[NSNumberFormatter alloc] init] autorelease];
    [formatter setMaximumFractionDigits:2];
    if (flags & kUnitStringLocalizedFormat) {
        [formatter setNumberStyle: NSNumberFormatterDecimalStyle];
    }
    // Beware of reusing this format string. -[NSString stringWithFormat] ignores \0, *printf does not.
    return [NSString stringWithFormat:@"%@ %cB", [formatter stringFromNumber: [NSNumber numberWithDouble: bytes]], units[exponent]];
}

默认情况下(如果 0 传递给 flags),它将输出 SI 单位(以十为底)。您可以设置kUnitStringBinaryUnits 来选择适合内存的二进制(以二为底)单位,或kUnitStringOSNativeUnits 以根据操作系统版本自动选择单位类型(Leopard 之前的以二为底,Leopard 之后的以十为底)。设置kUnitStringLocalizedFormat 根据用户当前的区域设置格式化字符串。例如:

unitStringFromBytes(1073741824, 0); // → "1.07 GB"
unitStringFromBytes(1073741824, kUnitStringBinaryUnits); // → "1 GB"
unitStringFromBytes(1073741824, kUnitStringOSNativeUnits | kUnitStringLocalizedFormat); // → "1.07 GB" (In Mac OS 10.6)
unitStringFromBytes(12345678901234567890123456789, kUnitStringOSNativeUnits | kUnitStringLocalizedFormat); // → "12,345.68 YB" (In Mac OS 10.6, in the US)
unitStringFromBytes(12345678901234567890123456789, kUnitStringOSNativeUnits | kUnitStringLocalizedFormat); // → "12.345,68 YB" (In Mac OS 10.6, in Spain)

这是操作系统原生单元所需的辅助函数:

BOOL leopardOrGreater(){
    static BOOL alreadyComputedOS = NO;
    static BOOL leopardOrGreater = NO;
    if (!alreadyComputedOS) {
        SInt32 majorVersion, minorVersion;
        Gestalt(gestaltSystemVersionMajor, &majorVersion);
        Gestalt(gestaltSystemVersionMinor, &minorVersion);
        leopardOrGreater = ((majorVersion == 10 && minorVersion >= 5) || majorVersion > 10);
        alreadyComputedOS = YES;
    }
    return leopardOrGreater;
}

【讨论】:

    【解决方案4】:
    NSString *stringFromFileSize(NSInteger theSize)
    {
        /*
         From http://snippets.dzone.com/posts/show/3038 with slight modification
         */
        float floatSize = theSize;
        if (theSize<1023)
            return([NSString stringWithFormat:@"%i bytes",theSize]);
        floatSize = floatSize / 1024;
        if (floatSize<1023)
            return([NSString stringWithFormat:@"%1.1f KB",floatSize]);
        floatSize = floatSize / 1024;
        if (floatSize<1023)
            return([NSString stringWithFormat:@"%1.1f MB",floatSize]);
        floatSize = floatSize / 1024;
    
        return([NSString stringWithFormat:@"%1.1f GB",floatSize]);
    }
    

    【讨论】:

    • 我建议将 'theSize' 参数设置为 'size_t' 类型,它是一个 64 位整数。上述方法将在 2 GB 后失败。
    • 请注意,使用 1024 作为基础而不是 1000 不符合人机界面指南,因此,任何使用此代码的应用都可能被 App Store 拒绝。
    • 带格式的字符串不考虑本地化(例如小数分隔符) - 这应该使用上面看到的数字格式化程序来完成
    • 我同意,NSInteger 不会保存一个无符号长长的数字,你会失去对十进制数字的控制。使用 NSNumberFormatter 更通用,也是更好的整体选择。
    【解决方案5】:

    这是一个更类似于 Objective C 的函数(使用 NSNumber、NSArray、NSStirng 等)来进行这种转换。

    这是基于 Sidncious 的回答,非常感谢您在那里完成的初步工作。同样基于维基百科的文章。

    一般这样使用它:[HumanReadableDataSizeHelper humanReadableSizeFromBytes:[NSNumber numberWithDouble:doubleValue]]

    但是,您似乎想要具有 1024 乘数的 SI 单位,因此您可以像这样使用它:[HumanReadableDataSizeHelper humanReadableSizeFromBytes:[NSNumber numberWithDouble:doubleValue] useSiPrefixes:YES useSiMultiplier:NO]

    我默认使用二进制前缀 (ki, Mi) 的原因是因为它们似乎是最适合用于计算机上数据大小的单位前缀集。您要求的是 SI 单位前缀,但使用 1024 的乘数,技术上不正确。虽然我会注意到 1024 的倍数的 SI 前缀相当普遍,并且二进制前缀不被很好地接受(根据 Wikipedia)。

    HumanReadableDataSizeHelper.h

    @interface HumanReadableDataSizeHelper : NSObject
    
    
    /**
        @brief  Produces a string containing the largest appropriate units and the new fractional value.
        @param  sizeInBytes  The value to convert in bytes.
    
        This function converts the bytes value to a value in the greatest units that produces a value >= 1 and returns the new value and units as a string.
    
        The magnitude multiplier used is 1024 and the prefixes used are the binary prefixes (ki, Mi, ...).
     */
    + (NSString *)humanReadableSizeFromBytes:(NSNumber *)sizeInBytes;
    
    /**
        @brief  Produces a string containing the largest appropriate units and the new fractional value.
        @param  sizeInBytes  The value to convert in bytes.
        @param  useSiPrefixes  Controls what prefix-set is used.
        @param  useSiMultiplier  Controls what magnitude multiplier is used.
    
        This function converts the bytes value to a value in the greatest units that produces a value >= 1 and returns the new value and units as a string.
    
        When useSiPrefixes is true, the prefixes used are the SI unit prefixes (k, M, ...).
        When useSiPrefixes is false, the prefixes used are the binary prefixes (ki, Mi, ...).
    
        When useSiMultiplier is true, the magnitude multiplier used is 1000
        When useSiMultiplier is false, the magnitude multiplier used is 1024.
     */
    + (NSString *)humanReadableSizeFromBytes:(NSNumber *)sizeInBytes  useSiPrefixes:(BOOL)useSiPrefixes  useSiMultiplier:(BOOL)useSiMultiplier;
    
    
    @end
    

    HumanReadableDataSizeHelper.m

    @implementation HumanReadableDataSizeHelper
    
    
    + (NSString *)humanReadableSizeFromBytes:(NSNumber *)sizeInBytes
    {
        return [self humanReadableSizeFromBytes:sizeInBytes  useSiPrefixes:NO  useSiMultiplier:NO];
    }
    
    
    + (NSString *)humanReadableSizeFromBytes:(NSNumber *)sizeInBytes  useSiPrefixes:(BOOL)useSiPrefixes  useSiMultiplier:(BOOL)useSiMultiplier
    {
        NSString *unitSymbol = @"B";
        NSInteger multiplier;
        NSArray *prefixes;
    
        if (useSiPrefixes)
        {
            /*  SI prefixes
             http://en.wikipedia.org/wiki/Kilo-
             kilobyte   (kB)    10^3    
             megabyte   (MB)    10^6    
             gigabyte   (GB)    10^9    
             terabyte   (TB)    10^12   
             petabyte   (PB)    10^15   
             exabyte    (EB)    10^18   
             zettabyte  (ZB)    10^21   
             yottabyte  (YB)    10^24   
             */
    
            prefixes = [NSArray arrayWithObjects: @"", @"k", @"M", @"G", @"T", @"P", @"E", @"Z", @"Y", nil];
        }
        else
        {
            /*  Binary prefixes
             http://en.wikipedia.org/wiki/Binary_prefix
             kibibyte   (KiB)   2^10 = 1.024 * 10^3
             mebibyte   (MiB)   2^20 ≈ 1.049 * 10^6
             gibibyte   (GiB)   2^30 ≈ 1.074 * 10^9
             tebibyte   (TiB)   2^40 ≈ 1.100 * 10^12
             pebibyte   (PiB)   2^50 ≈ 1.126 * 10^15
             exbibyte   (EiB)   2^60 ≈ 1.153 * 10^18
             zebibyte   (ZiB)   2^70 ≈ 1.181 * 10^21
             yobibyte   (YiB)   2^80 ≈ 1.209 * 10^24
             */
    
            prefixes = [NSArray arrayWithObjects: @"", @"ki", @"Mi", @"Gi", @"Ti", @"Pi", @"Ei", @"Zi", @"Yi", nil];
        }
    
        if (useSiMultiplier)
        {
            multiplier = 1000;
        }
        else
        {
            multiplier = 1024;
        }
    
        NSInteger exponent = 0;
        double size = [sizeInBytes doubleValue];
    
        while ( (size >= multiplier) && (exponent < [prefixes count]) )
        {
            size /= multiplier;
            exponent++;
        }
    
        NSNumberFormatter* formatter = [[[NSNumberFormatter alloc] init] autorelease];
        [formatter setMaximumFractionDigits:2];
        [formatter setNumberStyle:NSNumberFormatterDecimalStyle]; // Uses localized number formats.
    
        NSString *sizeInUnits = [formatter stringFromNumber:[NSNumber numberWithDouble:size]];
    
        return [NSString stringWithFormat:@"%@ %@%@", sizeInUnits, [prefixes objectAtIndex:exponent], unitSymbol];
    }
    
    
    @end
    

    【讨论】:

      【解决方案6】:

      您可以使用 FormatterKit 及其TTTUnitOfInformationFormatter 类:

      https://github.com/mattt/FormatterKit

      它也可以通过 CocoaPods 获得:

      pod 'FormatterKit', '~> 1.1.1'
      

      【讨论】:

        【解决方案7】:
        - (id)transformedValue:(id)value
        {
        
            double convertedValue = [value doubleValue];
            int multiplyFactor = 0;
        
            NSArray *tokens = @[@"bytes",@"KB",@"MB",@"GB",@"TB"];
        
            while (convertedValue > 1024) {
                convertedValue /= 1024;
                multiplyFactor++;
            }
        
            return [NSString stringWithFormat:@"%4.2f %@",convertedValue, tokens[multiplyFactor]];
        }
        

        【讨论】:

        • 您的代码很好,但在 OSX 上,您必须考虑 1000 的倍数而不是 1024,因为硬盘制造商以及 Finder,1 GB = 1000 MB :-)
        【解决方案8】:

        我知道问题是针对 Obj C 的,但如果有人在寻找 swift 版本:

         public static func fileSizeDisplay(fromBytes:Int) -> String {
                let display = ["bytes","KB","MB","GB","TB","PB"]
                var value:Double = Double(fromBytes)
                var type = 0
                while (value > 1024){
                    value /= 1024
                    type = type + 1
        
                }
                return "\(String(format:"%g", value)) \(display[type])"
        
            }
        

        【讨论】:

          猜你喜欢
          • 2014-02-04
          • 1970-01-01
          • 2012-05-12
          • 2012-03-31
          • 1970-01-01
          • 2012-03-25
          • 2012-03-09
          • 2018-08-17
          相关资源
          最近更新 更多