文件大小格式提供者


71

有没有简单的方法来创建使用IFormatProvider的类,该类写出用户友好的文件大小?

public static string GetFileSizeString(string filePath)
{
    FileInfo info = new FileInfo(@"c:\windows\notepad.exe");
    long size = info.Length;
    string sizeString = size.ToString(FileSizeFormatProvider); // This is where the class does its magic...
}

它应导致字符串格式设置为“ 2,5 MB ”,“ 3,9 GB ”,“ 670字节”等。



Answers:


111

我用这个,我从网上得到

public class FileSizeFormatProvider : IFormatProvider, ICustomFormatter
{
    public object GetFormat(Type formatType)
    {
        if (formatType == typeof(ICustomFormatter)) return this;
        return null;
    }

    private const string fileSizeFormat = "fs";
    private const Decimal OneKiloByte = 1024M;
    private const Decimal OneMegaByte = OneKiloByte * 1024M;
    private const Decimal OneGigaByte = OneMegaByte * 1024M;

    public string Format(string format, object arg, IFormatProvider formatProvider)
    {    
        if (format == null || !format.StartsWith(fileSizeFormat))    
        {    
            return defaultFormat(format, arg, formatProvider);    
        }

        if (arg is string)    
        {    
            return defaultFormat(format, arg, formatProvider);    
        }

        Decimal size;

        try    
        {    
            size = Convert.ToDecimal(arg);    
        }    
        catch (InvalidCastException)    
        {    
            return defaultFormat(format, arg, formatProvider);    
        }

        string suffix;
        if (size > OneGigaByte)
        {
            size /= OneGigaByte;
            suffix = "GB";
        }
        else if (size > OneMegaByte)
        {
            size /= OneMegaByte;
            suffix = "MB";
        }
        else if (size > OneKiloByte)
        {
            size /= OneKiloByte;
            suffix = "kB";
        }
        else
        {
            suffix = " B";
        }

        string precision = format.Substring(2);
        if (String.IsNullOrEmpty(precision)) precision = "2";
        return String.Format("{0:N" + precision + "}{1}", size, suffix);

    }

    private static string defaultFormat(string format, object arg, IFormatProvider formatProvider)
    {
        IFormattable formattableArg = arg as IFormattable;
        if (formattableArg != null)
        {
            return formattableArg.ToString(format, formatProvider);
        }
        return arg.ToString();
    }

}

使用的示例是:

Console.WriteLine(String.Format(new FileSizeFormatProvider(), "File size: {0:fs}", 100));
Console.WriteLine(String.Format(new FileSizeFormatProvider(), "File size: {0:fs}", 10000));

感谢http://flimflan.com/blog/FileSizeFormatProvider.aspx

ToString()有问题,它期望实现IFormatProvider的NumberFormatInfo类型,但NumberFormatInfo类是密封的:(

如果您使用的是C#3.0,则可以使用扩展方法来获得所需的结果:

public static class ExtensionMethods
{
    public static string ToFileSize(this long l)
    {
        return String.Format(new FileSizeFormatProvider(), "{0:fs}", l);
    }
}

您可以像这样使用它。

long l = 100000000;
Console.WriteLine(l.ToFileSize());

希望这可以帮助。


好答案。如果您进行编辑以包括用法示例,那就更好了。
AR。

1
只是为了澄清,答案已通过用法示例进行了更新。@EduardoCampañó,谢谢,它很棒!:D
Rami A.

46

好的,我不会将其包装为Format提供程序,而是要重新发明轮子,而是有一个Win32 api调用来根据我在各种应用程序中使用过很多次的提供的字节来格式化大小字符串。

[DllImport("Shlwapi.dll", CharSet = CharSet.Auto)]
public static extern long StrFormatByteSize( long fileSize, [MarshalAs(UnmanagedType.LPTStr)] StringBuilder buffer, int bufferSize );

因此,我想您应该可以使用该提供程序作为核心转换代码来组合提供程序。

这是指向StrFormatByteSize的MSDN规范的链接。


8
请注意,此函数返回本地化的字符串(数字分隔符可能有所不同),取决于您的实际需求,该字符串可能是好是坏。
西蒙·穆里耶

1
PInvoke.net站点提供了一个很好的示例,可以将其包装在一种可以轻松转换为扩展方法的方法中。pinvoke.net/default.aspx/shlwapi.strformatbytesize
詹姆斯

27

现在我意识到您实际上正在要求使用String.Format()的东西-我想我应该在发布之前阅读过两次该问题;-)

我不喜欢每次都必须显式传递格式提供程序的解决方案-从我从本文中可以得出的结论来看,解决此问题的最佳方法是实现FileSize类型,实现IFormattable接口。

我继续并实现了一个支持此接口的结构,该结构可以从整数强制转换。在我自己的与文件相关的API中,我将让我的.FileSize属性返回一个FileSize实例。

这是代码:

using System.Globalization;

public struct FileSize : IFormattable
{
    private ulong _value;

    private const int DEFAULT_PRECISION = 2;

    private static IList<string> Units;

    static FileSize()
    {
        Units = new List<string>(){
            "B", "KB", "MB", "GB", "TB"
        };
    }

    public FileSize(ulong value)
    {
        _value = value;
    }

    public static explicit operator FileSize(ulong value)
    {
        return new FileSize(value);
    }

    override public string ToString()
    {
        return ToString(null, null);
    }

    public string ToString(string format)
    {
        return ToString(format, null);
    }

    public string ToString(string format, IFormatProvider formatProvider)
    {
        int precision;

        if (String.IsNullOrEmpty(format))
            return ToString(DEFAULT_PRECISION);
        else if (int.TryParse(format, out precision))
            return ToString(precision);
        else
            return _value.ToString(format, formatProvider);
    }

    /// <summary>
    /// Formats the FileSize using the given number of decimals.
    /// </summary>
    public string ToString(int precision)
    {
        double pow = Math.Floor((_value > 0 ? Math.Log(_value) : 0) / Math.Log(1024));
        pow = Math.Min(pow, Units.Count - 1);
        double value = (double)_value / Math.Pow(1024, pow);
        return value.ToString(pow == 0 ? "F0" : "F" + precision.ToString()) + " " + Units[(int)pow];
    }
}

还有一个简单的单元测试,演示了其工作原理:

    [Test]
    public void CanUseFileSizeFormatProvider()
    {
        Assert.AreEqual(String.Format("{0}", (FileSize)128), "128 B");
        Assert.AreEqual(String.Format("{0}", (FileSize)1024), "1.00 KB");
        Assert.AreEqual(String.Format("{0:0}", (FileSize)10240), "10 KB");
        Assert.AreEqual(String.Format("{0:1}", (FileSize)102400), "100.0 KB");
        Assert.AreEqual(String.Format("{0}", (FileSize)1048576), "1.00 MB");
        Assert.AreEqual(String.Format("{0:D}", (FileSize)123456), "123456");

        // You can also manually invoke ToString(), optionally with the precision specified as an integer:
        Assert.AreEqual(((FileSize)111111).ToString(2), "108.51 KB");
    }

如您所见,现在可以正确设置FileSize类型的格式,并且还可以指定小数位数,以及在需要时应用常规数字格式。

我想您可以更进一步,例如允许显式格式选择,例如“ {0:KB}”强制以千字节为单位进行格式化。但是我要留在这里。

我还在下面留下我的第一篇文章,这两个人不想使用格式化API ...


剥皮猫的100种方法,但这是我的方法-向int类型添加扩展方法:

public static class IntToBytesExtension
{
    private const int PRECISION = 2;

    private static IList<string> Units;

    static IntToBytesExtension()
    {
        Units = new List<string>(){
            "B", "KB", "MB", "GB", "TB"
        };
    }

    /// <summary>
    /// Formats the value as a filesize in bytes (KB, MB, etc.)
    /// </summary>
    /// <param name="bytes">This value.</param>
    /// <returns>Filesize and quantifier formatted as a string.</returns>
    public static string ToBytes(this int bytes)
    {
        double pow = Math.Floor((bytes>0 ? Math.Log(bytes) : 0) / Math.Log(1024));
        pow = Math.Min(pow, Units.Count-1);
        double value = (double)bytes / Math.Pow(1024, pow);
        return value.ToString(pow==0 ? "F0" : "F" + PRECISION.ToString()) + " " + Units[(int)pow];
    }
}

在程序集中具有此扩展名的情况下,要格式化文件大小,只需使用(1234567).ToBytes()之类的语句即可。

以下MbUnit测试准确地阐明了输出内容:

    [Test]
    public void CanFormatFileSizes()
    {
        Assert.AreEqual("128 B", (128).ToBytes());
        Assert.AreEqual("1.00 KB", (1024).ToBytes());
        Assert.AreEqual("10.00 KB", (10240).ToBytes());
        Assert.AreEqual("100.00 KB", (102400).ToBytes());
        Assert.AreEqual("1.00 MB", (1048576).ToBytes());
    }

而且,您可以轻松地将单位和精度更改为适合您的需求:-)


2
我唯一看到的问题是扩展方法“ ToBytes”,我认为它可能与将int转换为字节的方法(如您所知,byte是C#中的数据类型)相混淆。最好将其重命名为其他名称,也许是“ AsByteCount”。否则,我认为两者都是好的解决方案。
Skurmedel

+1请注意,单元测试在顶部解决方案中是倒置的(应为Assert.AreEqual(期望,实际)),底部是正确的参数顺序。
2011年

该解决方案与我发布的解决方案大致相同。:)最大的不同是我的实现支持更多格式(长,短,kB和kiB)。但是也不错!
Corniel Nobel '18

10

这是我知道的格式化文件大小的最简单的实现:

public string SizeText
{
    get
    {
        var units = new[] { "B", "KB", "MB", "GB", "TB" };
        var index = 0;
        double size = Size;
        while (size > 1024)
        {
            size /= 1024;
            index++;
        }
        return string.Format("{0:2} {1}", size, units[index]);
    }
}

而Size是未格式化的文件大小(以字节为单位)。

问候基督徒

http://www.wpftutorial.net


5

我的代码...感谢Shaun Austin。

[DllImport("Shlwapi.dll", CharSet = CharSet.Auto)]
public static extern long StrFormatByteSize(long fileSize, [MarshalAs(UnmanagedType.LPTStr)] StringBuilder buffer, int bufferSize);

public void getFileInfo(string filename)
{
    System.IO.FileInfo fileinfo = new FileInfo(filename);
    this.FileName.Text = fileinfo.Name;
    StringBuilder buffer = new StringBuilder();
    StrFormatByteSize(fileinfo.Length, buffer, 100);
    this.FileSize.Text = buffer.ToString();
}

3

因为换档是很便宜的操作

public static string ToFileSize(this long size)
{
    if (size < 1024)
    {
        return (size).ToString("F0") + " bytes";
    }
    else if ((size >> 10) < 1024)
    {
        return (size/(float)1024).ToString("F1") + " KB";
    }
    else if ((size >> 20) < 1024)
    {
        return ((size >> 10) / (float)1024).ToString("F1") + " MB";
    }
    else if ((size >> 30) < 1024)
    {
        return ((size >> 20) / (float)1024).ToString("F1") + " GB";
    }
    else if ((size >> 40) < 1024)
    {
        return ((size >> 30) / (float)1024).ToString("F1") + " TB";
    }
    else if ((size >> 50) < 1024)
    {
        return ((size >> 40) / (float)1024).ToString("F1") + " PB";
    }
    else
    {
        return ((size >> 50) / (float)1024).ToString("F0") + " EB";
    }
}

2

我需要一个可以针对不同区域性进行本地化的版本(十进制分隔符,“字节”翻译),并支持所有可能的二进制前缀(最多Exa)。这是一个演示如何使用它的示例:

// force "en-US" culture for tests
Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo(1033); 

// Displays "8.00 EB"
Console.WriteLine(FormatFileSize(long.MaxValue)); 

// Use "fr-FR" culture. Displays "20,74 ko", o is for "octet"
Console.WriteLine(FormatFileSize(21234, "o", null, CultureInfo.GetCultureInfo(1036)));

这是代码:

    /// <summary>
    /// Converts a numeric value into a string that represents the number expressed as a size value in bytes, kilobytes, megabytes, gigabytes, terabytes, petabytes or exabytes, depending on the size
    /// </summary>
    /// <param name="size">The size.</param>
    /// <returns>
    /// The number converted.
    /// </returns>
    public static string FormatFileSize(long size)
    {
        return FormatFileSize(size, null, null, null);
    }

    /// <summary>
    /// Converts a numeric value into a string that represents the number expressed as a size value in bytes, kilobytes, megabytes, gigabytes, terabytes, petabytes or exabytes, depending on the size
    /// </summary>
    /// <param name="size">The size.</param>
    /// <param name="byteName">The string used for the byte name. If null is passed, "B" will be used.</param>
    /// <param name="numberFormat">The number format. If null is passed, "N2" will be used.</param>
    /// <param name="formatProvider">The format provider. May be null to use current culture.</param>
    /// <returns>The number converted.</returns>
    public static string FormatFileSize(long size, string byteName, string numberFormat, IFormatProvider formatProvider)
    {
        if (size < 0)
            throw new ArgumentException(null, "size");

        if (byteName == null)
        {
            byteName = "B";
        }

        if (string.IsNullOrEmpty(numberFormat))
        {
            numberFormat = "N2";
        }

        const decimal K = 1024;
        const decimal M = K * K;
        const decimal G = M * K;
        const decimal T = G * K;
        const decimal P = T * K;
        const decimal E = P * K;

        decimal dsize = size;

        string suffix = null;
        if (dsize >= E)
        {
            dsize /= E;
            suffix = "E";
        }
        else if (dsize >= P)
        {
            dsize /= P;
            suffix = "P";
        }
        else if (dsize >= T)
        {
            dsize /= T;
            suffix = "T";
        }
        else if (dsize >= G)
        {
            dsize /= G;
            suffix = "G";
        }
        else if (dsize >= M)
        {
            dsize /= M;
            suffix = "M";
        }
        else if (dsize >= K)
        {
            dsize /= K;
            suffix = "k";
        }
        if (suffix != null)
        {
            suffix = " " + suffix;
        }
        return string.Format(formatProvider, "{0:" + numberFormat + "}" + suffix + byteName, dsize);
    }

2

这是更精确的扩展:

    public static string FileSizeFormat(this long lSize)
    {
        double size = lSize;
        int index = 0;
        for(; size > 1024; index++)
            size /= 1024;
        return size.ToString("0.000 " + new[] { "B", "KB", "MB", "GB", "TB" }[index]);          
    }

一个do...while循环是更好的在我看来,但一个不错的选择。克里斯蒂安·摩瑟(Christian Moser)的回应的一部分副本-只是使其成为一种方法。但是我想可能有些人不理解,但是您可以将他的回应转变为这种方式。
vapcguy 2015年

2

可以在这里找到域驱动方法:https : //github.com/Corniel/Qowaiv/blob/master/src/Qowaiv/IO/StreamSize.cs

StreamSize结构体是流大小的一种表示形式,它使您既可以使用适当的扩展名自动进行格式设置,又可以以KB / MB或其他形式指定所需的大小。这有很多优点,不仅因为您可以立即使用格式设置,而且还可以帮助您创建更好的模型,显而易见的是,方法的属性或结果表示流的大小。它还具有文件大小扩展名:GetStreamSize(此FileInfo文件)。

简写

  • 新的StreamSize(8900).ToString(“ s”)=> 8900b
  • 新的StreamSize(238900).ToString(“ s”)=> 238.9kb
  • 新的StreamSize(238900).ToString(“ S”)=> 238.9 kB
  • 新的StreamSize(238900).ToString(“ 0000.00 S”)=> 0238.90 kB

完整的符号

  • 新的StreamSize(8900).ToString(“ 0.0 f”)=> 8900.0字节
  • 新的StreamSize(238900).ToString(“ 0 f”)=> 234 KB
  • 新的StreamSize(1238900).ToString(“ 0.00 F”)=> 1.24兆字节

自订

  • 新的StreamSize(8900).ToString(“ 0.0 kb”)=> 8.9 kb
  • 新的StreamSize(238900).ToString(“ 0.0 MB”)=> 0.2 MB
  • new StreamSize(1238900).ToString(“#,## 0.00千字节”)=> 1,239.00千字节
  • 新的StreamSize(1238900).ToString(“#,## 0”)=> 1,238,900

有一个NuGet软件包,因此您可以使用其中一个:https : //www.nuget.org/packages/Qowaiv


1

我接受了Eduardo的回答,并将其与其他地方的类似示例结合在一起,以提供格式的其他选项。

public class FileSizeFormatProvider : IFormatProvider, ICustomFormatter
{
   public object GetFormat(Type formatType)
   {
      if (formatType == typeof(ICustomFormatter))
      {
         return this;
      }

      return null;
   }

   private const string fileSizeFormat = "FS";
   private const string kiloByteFormat = "KB";
   private const string megaByteFormat = "MB";
   private const string gigaByteFormat = "GB";
   private const string byteFormat = "B";
   private const Decimal oneKiloByte = 1024M;
   private const Decimal oneMegaByte = oneKiloByte * 1024M;
   private const Decimal oneGigaByte = oneMegaByte * 1024M;

   public string Format(string format, object arg, IFormatProvider formatProvider)
   {
      //
      // Ensure the format provided is supported
      //
      if (String.IsNullOrEmpty(format) || !(format.StartsWith(fileSizeFormat, StringComparison.OrdinalIgnoreCase) ||
                                            format.StartsWith(kiloByteFormat, StringComparison.OrdinalIgnoreCase) ||
                                            format.StartsWith(megaByteFormat, StringComparison.OrdinalIgnoreCase) ||
                                            format.StartsWith(gigaByteFormat, StringComparison.OrdinalIgnoreCase)))
      {
         return DefaultFormat(format, arg, formatProvider);
      }

      //
      // Ensure the argument type is supported
      //
      if (!(arg is long || arg is decimal || arg is int))
      {
         return DefaultFormat(format, arg, formatProvider);
      }

      //
      // Try and convert the argument to decimal
      //
      Decimal size;

      try
      {
         size = Convert.ToDecimal(arg);
      }
      catch (InvalidCastException)
      {
         return DefaultFormat(format, arg, formatProvider);
      }

      //
      // Determine the suffix to use and convert the argument to the requested size
      //
      string suffix;

      switch (format.Substring(0, 2).ToUpper())
      {
         case kiloByteFormat:
            size = size / oneKiloByte;
            suffix = kiloByteFormat;
            break;
         case megaByteFormat:
            size = size / oneMegaByte;
            suffix = megaByteFormat;
            break;
         case gigaByteFormat:
            size = size / oneGigaByte;
            suffix = gigaByteFormat;
            break;
         case fileSizeFormat:
            if (size > oneGigaByte)
            {
               size /= oneGigaByte;
               suffix = gigaByteFormat;
            }
            else if (size > oneMegaByte)
            {
               size /= oneMegaByte;
               suffix = megaByteFormat;
            }
            else if (size > oneKiloByte)
            {
               size /= oneKiloByte;
               suffix = kiloByteFormat;
            }
            else
            {
               suffix = byteFormat;
            }
            break;
         default:
            suffix = byteFormat;
            break;
      }

      //
      // Determine the precision to use
      //
      string precision = format.Substring(2);

      if (String.IsNullOrEmpty(precision))
      {
         precision = "2";
      }

      return String.Format("{0:N" + precision + "}{1}", size, suffix);
   }

   private static string DefaultFormat(string format, object arg, IFormatProvider formatProvider)
   {
      IFormattable formattableArg = arg as IFormattable;

      if (formattableArg != null)
      {
         return formattableArg.ToString(format, formatProvider);
      }

      return arg.ToString();
   }
}

1

如果您更改:

      if (String.IsNullOrEmpty(precision))
      {
         precision = "2";
      }

进入

      if (String.IsNullOrEmpty(precision))
      {
        if (size < 10)
        {
           precision = "2";
        }
        else if (size < 100)
        {
            precision = "1";
        }
        else
        {
           precision = "0";
        }
      }

没有附加精度说明符的结果(因此只有0:fs而不是0:fs3)将通过将精度调整为大小而开始模仿Win32的StrFormatByteSize()。

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.