Czy .NET zapewnia łatwy sposób konwersji bajtów na KB, MB, GB itp.?


112

Zastanawiam się tylko, czy .NET zapewnia czysty sposób na zrobienie tego:

int64 x = 1000000;
string y = null;
if (x / 1024 == 0) {
    y = x + " bytes";
}
else if (x / (1024 * 1024) == 0) {
    y = string.Format("{0:n1} KB", x / 1024f);
}

itp...

Odpowiedzi:


193

Oto dość zwięzły sposób, aby to zrobić:

static readonly string[] SizeSuffixes = 
                   { "bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" };
static string SizeSuffix(Int64 value, int decimalPlaces = 1)
{
    if (decimalPlaces < 0) { throw new ArgumentOutOfRangeException("decimalPlaces"); }
    if (value < 0) { return "-" + SizeSuffix(-value); } 
    if (value == 0) { return string.Format("{0:n" + decimalPlaces + "} bytes", 0); }

    // mag is 0 for bytes, 1 for KB, 2, for MB, etc.
    int mag = (int)Math.Log(value, 1024);

    // 1L << (mag * 10) == 2 ^ (10 * mag) 
    // [i.e. the number of bytes in the unit corresponding to mag]
    decimal adjustedSize = (decimal)value / (1L << (mag * 10));

    // make adjustment when the value is large enough that
    // it would round up to 1000 or more
    if (Math.Round(adjustedSize, decimalPlaces) >= 1000)
    {
        mag += 1;
        adjustedSize /= 1024;
    }

    return string.Format("{0:n" + decimalPlaces + "} {1}", 
        adjustedSize, 
        SizeSuffixes[mag]);
}

A oto oryginalna implementacja, którą zasugerowałem, która może być nieznacznie wolniejsza, ale nieco łatwiejsza do naśladowania:

static readonly string[] SizeSuffixes = 
                  { "bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" };

static string SizeSuffix(Int64 value, int decimalPlaces = 1)
{
    if (value < 0) { return "-" + SizeSuffix(-value); } 

    int i = 0;
    decimal dValue = (decimal)value;
    while (Math.Round(dValue, decimalPlaces) >= 1000)
    {
        dValue /= 1024;
        i++;
    }

    return string.Format("{0:n" + decimalPlaces + "} {1}", dValue, SizeSuffixes[i]);
}

Console.WriteLine(SizeSuffix(100005000L));

Należy pamiętać o jednej rzeczy - w notacji SI „kilo” zwykle używa małej litery k, podczas gdy wszystkie większe jednostki używają dużej litery. Windows używa KB, MB, GB, więc użyłem KB powyżej, ale możesz zamiast tego rozważyć kB.


Pytający szuka tylko 1 miejsca po przecinku dokładności. Czy możesz podać przykład danych wejściowych, które generują niepoprawne dane wyjściowe?
JLRishe

2
Oba przykłady używają teraz dzielenia zmiennoprzecinkowego, więc nie powinno się martwić o błędy zaokrągleń.
JLRishe,

Dziękuję, właśnie tego szukałem. (2. realizacja.)
snapplex

1
Bardzo fajna realizacja. Zwróć uwagę, że jeśli przekażesz wartość 0 do tej funkcji, zgłosi ona wyjątek IndexOutOfRangeException. Postanowiłem dodać if (value == 0) { return "0"; }czek wewnątrz funkcji.
Bounav

Czy możesz podać przypadek, gdy rozmiar pliku jest <0? Dla mnie wygląda to dziwnie ...
Ruslan F.

85

Checkout ByteSize bibliotekę. To jest System.TimeSpandla bajtów!

Obsługuje konwersję i formatowanie za Ciebie.

var maxFileSize = ByteSize.FromKiloBytes(10);
maxFileSize.Bytes;
maxFileSize.MegaBytes;
maxFileSize.GigaBytes;

Wykonuje również reprezentację ciągu i analizuje.

// ToString
ByteSize.FromKiloBytes(1024).ToString(); // 1 MB
ByteSize.FromGigabytes(.5).ToString();   // 512 MB
ByteSize.FromGigabytes(1024).ToString(); // 1 TB

// Parsing
ByteSize.Parse("5b");
ByteSize.Parse("1.55B");

6
Prosty w użyciu i zrozumiały, działa z .Net 4.0 i nowszymi wersjami.
Joker

34
Powinno to być zawarte w ramach .NET Framework
helios456

Jedynym problemem, jaki widzę, jest to, że metody konwersji działają tylko od bajtów do bajtów, ale nie na odwrót.
SuperJMN,

@SuperJMN co masz na myśli jako non-bajt? Jak bity? Istnieje metoda .FromBits, której możesz użyć.
Omar,

1
Jeśli twoje dane źródłowe są czymś innym niż "bajty" i musisz mieć możliwość konwersji na cokolwiek ... to jest biblioteka, której powinieneś używać.
James Blake,

38

Ponieważ wszyscy inni publikują swoje metody, pomyślałem, że opublikuję metodę rozszerzenia, której zwykle używam do tego:

EDYCJA: dodano warianty int / long ... i naprawiono literówkę copypasta ...

public static class Ext
{
    private const long OneKb = 1024;
    private const long OneMb = OneKb * 1024;
    private const long OneGb = OneMb * 1024;
    private const long OneTb = OneGb * 1024;

    public static string ToPrettySize(this int value, int decimalPlaces = 0)
    {
        return ((long)value).ToPrettySize(decimalPlaces);
    }

    public static string ToPrettySize(this long value, int decimalPlaces = 0)
    {
        var asTb = Math.Round((double)value / OneTb, decimalPlaces);
        var asGb = Math.Round((double)value / OneGb, decimalPlaces);
        var asMb = Math.Round((double)value / OneMb, decimalPlaces);
        var asKb = Math.Round((double)value / OneKb, decimalPlaces);
        string chosenValue = asTb > 1 ? string.Format("{0}Tb",asTb)
            : asGb > 1 ? string.Format("{0}Gb",asGb)
            : asMb > 1 ? string.Format("{0}Mb",asMb)
            : asKb > 1 ? string.Format("{0}Kb",asKb)
            : string.Format("{0}B", Math.Round((double)value, decimalPlaces));
        return chosenValue;
    }
}

Należy jednak pamiętać, że małe litery b zazwyczaj oznaczają bity, a nie bajty. :-) en.wikipedia.org/wiki/Data-rate_units#Kilobit_per_second
SharpC

32

Chciałbym go rozwiązać za pomocą Extension methods, Math.Powfunkcji oraz Enums:

public static class MyExtension
{
    public enum SizeUnits
    {
        Byte, KB, MB, GB, TB, PB, EB, ZB, YB
    }

    public static string ToSize(this Int64 value, SizeUnits unit)
    {
        return (value / (double)Math.Pow(1024, (Int64)unit)).ToString("0.00");
    }
}

i używaj go jak:

string h = x.ToSize(MyExtension.SizeUnits.KB);

3
Eleganckie rozwiązanie!
yossico

1
Skorzystałem z Twojego pomysłu, aby stworzyć taki, który automatycznie określa jednostkę. +1
Louis Somers

2
To bardzo eleganckie rozwiązanie, które jest znacznie czystsze i jest zgodne z zatwierdzonym rozwiązaniem. Jednak ściśle mówiąc, w oparciu o wartości wyliczenia, powinien opierać się na potędze 1000, a nie na 1024 ( en.wikipedia.org/wiki/Terabyte ) kod ... publiczny ciąg statyczny ToSize (ta długa wartość, jednostka jednostka) => $ "{wartość / Math.Pow (1000, (long) unit): F2} {unit.ToString ()}";
stoj

6

Krótka wersja najczęściej głosowanej odpowiedzi zawiera problemy z wartościami TB.

Dostosowałem go odpowiednio, aby obsługiwał również wartości tb i nadal bez pętli, a także dodałem trochę sprawdzania błędów pod kątem wartości ujemnych. Oto moje rozwiązanie:

static readonly string[] SizeSuffixes = { "bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" };
static string SizeSuffix(long value, int decimalPlaces = 0)
{
    if (value < 0)
    {
        throw new ArgumentException("Bytes should not be negative", "value");
    }
    var mag = (int)Math.Max(0, Math.Log(value, 1024));
    var adjustedSize = Math.Round(value / Math.Pow(1024, mag), decimalPlaces);
    return String.Format("{0} {1}", adjustedSize, SizeSuffixes[mag]);
}

1
Podany problem z dużymi wartościami nie powinien już występować w przyjętej odpowiedzi.
JLRishe

5

Nie. Głównie dlatego, że jest to raczej niszowa potrzeba i istnieje zbyt wiele możliwych wariantów. (Czy to „KB”, „Kb” czy „Ko”? Czy megabajt to 1024 * 1024 bajty, czy 1024 * 1000 bajtów? - tak, w niektórych miejscach jest to używane!)


1
+1 - według Wikipedii , kb => 1000 bajtów, a KiB => 1024 bajty.
Peter Majeed,

5

Oto opcja, która jest łatwiejsza do rozszerzenia niż twoja, ale nie, nie ma żadnej wbudowanej w samą bibliotekę.

private static List<string> suffixes = new List<string> { " B", " KB", " MB", " GB", " TB", " PB" };
public static string Foo(int number)
{
    for (int i = 0; i < suffixes.Count; i++)
    {
        int temp = number / (int)Math.Pow(1024, i + 1);
        if (temp == 0)
            return (number / (int)Math.Pow(1024, i)) + suffixes[i];
    }
    return number.ToString();
}

4
    private string GetFileSize(double byteCount)
    {
        string size = "0 Bytes";
        if (byteCount >= 1073741824.0)
            size = String.Format("{0:##.##}", byteCount / 1073741824.0) + " GB";
        else if (byteCount >= 1048576.0)
            size = String.Format("{0:##.##}", byteCount / 1048576.0) + " MB";
        else if (byteCount >= 1024.0)
            size = String.Format("{0:##.##}", byteCount / 1024.0) + " KB";
        else if (byteCount > 0 && byteCount < 1024.0)
            size = byteCount.ToString() + " Bytes";

        return size;
    }

    private void btnBrowse_Click(object sender, EventArgs e)
    {
        if (openFile1.ShowDialog() == DialogResult.OK)
        {
            FileInfo thisFile = new FileInfo(openFile1.FileName);

            string info = "";

            info += "File: " + Path.GetFileName(openFile1.FileName);
            info += Environment.NewLine;
            info += "File Size: " + GetFileSize((int)thisFile.Length);

            label1.Text = info;
        }
    }

To jest jeden ze sposobów, aby to zrobić (numer 1073741824.0 pochodzi z 1024 * 1024 * 1024 aka GB)


3

@ Odpowiedź Servy była miła i zwięzła. Myślę, że może być jeszcze prostsze?

private static string[] suffixes = new [] { " B", " KB", " MB", " GB", " TB", " PB" };

public static string ToSize(double number, int precision = 2)
{
    // unit's number of bytes
    const double unit = 1024;
    // suffix counter
    int i = 0;
    // as long as we're bigger than a unit, keep going
    while(number > unit)
    {
        number /= unit;
        i++;
    }
    // apply precision and current suffix
    return Math.Round(number, precision) + suffixes[i];
}

3

Oparty na eleganckim rozwiązaniu NeverHopeless:

private static readonly KeyValuePair<long, string>[] Thresholds = 
{
    // new KeyValuePair<long, string>(0, " Bytes"), // Don't devide by Zero!
    new KeyValuePair<long, string>(1, " Byte"),
    new KeyValuePair<long, string>(2, " Bytes"),
    new KeyValuePair<long, string>(1024, " KB"),
    new KeyValuePair<long, string>(1048576, " MB"), // Note: 1024 ^ 2 = 1026 (xor operator)
    new KeyValuePair<long, string>(1073741824, " GB"),
    new KeyValuePair<long, string>(1099511627776, " TB"),
    new KeyValuePair<long, string>(1125899906842620, " PB"),
    new KeyValuePair<long, string>(1152921504606850000, " EB"),

    // These don't fit into a int64
    // new KeyValuePair<long, string>(1180591620717410000000, " ZB"), 
    // new KeyValuePair<long, string>(1208925819614630000000000, " YB") 
};

/// <summary>
/// Returns x Bytes, kB, Mb, etc... 
/// </summary>
public static string ToByteSize(this long value)
{
    if (value == 0) return "0 Bytes"; // zero is plural
    for (int t = Thresholds.Length - 1; t > 0; t--)
        if (value >= Thresholds[t].Key) return ((double)value / Thresholds[t].Key).ToString("0.00") + Thresholds[t].Value;
    return "-" + ToByteSize(-value); // negative bytes (common case optimised to the end of this routine)
}

Może jest zbyt wiele komentarzy, ale zwykle zostawiam je, aby nie popełniać tych samych błędów podczas przyszłych wizyt ...



1

Połączyłem niektóre odpowiedzi w dwie metody, które działają świetnie. Druga metoda poniżej konwertuje z ciągu bajtów (na przykład 1,5,1 GB) z powrotem na bajty (na przykład 1621350140) jako wartość typu długiego. Mam nadzieję, że jest to przydatne dla innych, którzy szukają rozwiązania do konwersji bajtów na ciąg i z powrotem na bajty.

public static string BytesAsString(float bytes)
{
    string[] suffix = { "B", "KB", "MB", "GB", "TB" };
    int i;
    double doubleBytes = 0;

    for (i = 0; (int)(bytes / 1024) > 0; i++, bytes /= 1024)
    {
        doubleBytes = bytes / 1024.0;
    }

    return string.Format("{0:0.00} {1}", doubleBytes, suffix[i]);
}

public static long StringAsBytes(string bytesString)
{
    if (string.IsNullOrEmpty(bytesString))
    {
        return 0;
    }

    const long OneKb = 1024;
    const long OneMb = OneKb * 1024;
    const long OneGb = OneMb * 1024;
    const long OneTb = OneGb * 1024;
    double returnValue;
    string suffix = string.Empty;

    if (bytesString.IndexOf(" ") > 0)
    {
        returnValue = float.Parse(bytesString.Substring(0, bytesString.IndexOf(" ")));
        suffix = bytesString.Substring(bytesString.IndexOf(" ") + 1).ToUpperInvariant();
    }
    else
    {
        returnValue = float.Parse(bytesString.Substring(0, bytesString.Length - 2));
        suffix = bytesString.ToUpperInvariant().Substring(bytesString.Length - 2);
    }

    switch (suffix)
    {
        case "KB":
            {
                returnValue *= OneKb;
                break;
            }

        case "MB":
            {
                returnValue *= OneMb;
                break;
            }

        case "GB":
            {
                returnValue *= OneGb;
                break;
            }

        case "TB":
            {
                returnValue *= OneTb;
                break;
            }

        default:
            {
                break;
            }
    }

    return Convert.ToInt64(returnValue);
}

Mogę zapytać, dlaczego używasz float.Parsedo double?
John_J

1

Wiem, że to już stary wątek. ale może ktoś będzie szukał rozwiązania. A oto czego używam i najłatwiejszy sposób

  public static string FormatFileSize(long bytes) 
    {
        var unit = 1024;
        if (bytes < unit)
        {
            return $"{bytes} B";
        }
        var exp = (int)(Math.Log(bytes) / Math.Log(unit));
        return $"{bytes / Math.Pow(unit, exp):F2} " +
               $"{("KMGTPE")[exp - 1]}B";
    }

0

Co powiesz na:

public void printMB(uint sizekB)   
{
    double sizeMB = (double) sizekB / 1024;
    Console.WriteLine("Size is " + sizeMB.ToString("0.00") + "MB");
}

Np. Zadzwoń jak

printMB(123456);

Spowoduje to wyjście

"Size is 120,56 MB"

0

Poszedłem na rozwiązanie JerKimballs i kciuki za to. Chciałbym jednak dodać / wskazać, że jest to rzeczywiście kwestia kontrowersji jako całości. W swoich badaniach (z innych powodów) otrzymałem następujące informacje.

Kiedy zwykli ludzie (słyszałem, że istnieją) mówią o gigabajtach, odnoszą się do systemu metrycznego, w którym 1000 do potęgi 3 z pierwotnej liczby bajtów == liczba gigabajtów. Jednak oczywiście istnieją standardy IEC / JEDEC, które są ładnie podsumowane w Wikipedii, które zamiast 1000 do potęgi x mają 1024. Co dla fizycznych urządzeń magazynujących (i chyba logiczne, takie jak amazon i inne) oznacza stale rosnąca różnica między metrycznymi a IEC. Na przykład 1 TB == 1 terabajt metryczny to 1000 do potęgi 4, ale IEC oficjalnie określa podobną liczbę jako 1 TiB, tebibajt jako 1024 do potęgi 4. Ale, niestety, w zastosowaniach nietechnicznych (chciałbym według odbiorców) normą jest metryczna, aw mojej własnej aplikacji do użytku wewnętrznego obecnie wyjaśniam różnicę w dokumentacji. Ale do celów wyświetlania nie oferuję niczego poza metrycznymi. Wewnętrznie, mimo że nie ma to znaczenia w mojej aplikacji, przechowuję tylko bajty i wykonuję obliczenia do wyświetlenia.

Na marginesie uważam, że jest nieco nijakie, że struktura .Net AFAIK (i często się mylę, dziękuję władzom, które są), nawet w swojej wersji 4.5, nie zawiera nic na ten temat w żadnej bibliotece wewnętrznie. Można by się spodziewać, że pewnego rodzaju biblioteka open source będzie w pewnym momencie NuGettable, ale przyznaję, że jest to mały problem. Z drugiej strony System.IO.DriveInfo i inne również mają tylko bajty (tak długie), co jest raczej jasne.


0
public static class MyExtension
{
    public static string ToPrettySize(this float Size)
    {
        return ConvertToPrettySize(Size, 0);
    }
    public static string ToPrettySize(this int Size)
    {
        return ConvertToPrettySize(Size, 0);
    }
    private static string ConvertToPrettySize(float Size, int R)
    {
        float F = Size / 1024f;
        if (F < 1)
        {
            switch (R)
            {
                case 0:
                    return string.Format("{0:0.00} byte", Size);
                case 1:
                    return string.Format("{0:0.00} kb", Size);
                case 2:
                    return string.Format("{0:0.00} mb", Size);
                case 3:
                    return string.Format("{0:0.00} gb", Size);
            }
        }
        return ConvertToPrettySize(F, ++R);
    }
}

0

A co z rekursją:

private static string ReturnSize(double size, string sizeLabel)
{
  if (size > 1024)
  {
    if (sizeLabel.Length == 0)
      return ReturnSize(size / 1024, "KB");
    else if (sizeLabel == "KB")
      return ReturnSize(size / 1024, "MB");
    else if (sizeLabel == "MB")
      return ReturnSize(size / 1024, "GB");
    else if (sizeLabel == "GB")
      return ReturnSize(size / 1024, "TB");
    else
      return ReturnSize(size / 1024, "PB");
  }
  else
  {
    if (sizeLabel.Length > 0)
      return string.Concat(size.ToString("0.00"), sizeLabel);
    else
      return string.Concat(size.ToString("0.00"), "Bytes");
  }
}

Wtedy możesz to nazwać:

ReturnSize(size, string.Empty);

0

Jak napisano powyżej, rekurencja jest ulubionym sposobem, przy pomocy logarytmu.

Następująca funkcja ma 3 argumenty: dane wejściowe, ograniczenie wymiaru wyniku, czyli trzeci argument.

int ByteReDim(unsigned long ival, int constraint, unsigned long *oval)
{
    int base = 1 + (int) log10(ival);

    (*oval) = ival;
    if (base > constraint) {
        (*oval) = (*oval) >> 10;
        return(1 + ByteReDim((*oval), constraint, oval));
    } else
        return(0);
}

Teraz przekonwertujmy 12 GB pamięci RAM na kilka jednostek:

int main(void)
{
    unsigned long RAM;
    int unit; // index of below symbols array
    char symbol[5] = {'B', 'K', 'M', 'G', 'T'};

    unit = ByteReDim(12884901888, 12, &RAM);
    printf("%lu%c\n", RAM, symbol[unit]); // output is 12884901888B

    unit = ByteReDim(12884901888, 9, &RAM);
    printf("%lu%c\n", RAM, symbol[unit]); // output is 12582912K

    unit = ByteReDim(12884901888, 6, &RAM);
    printf("%lu%c\n", RAM, symbol[unit]); // output is 12288M

    unit = ByteReDim(12884901888, 3, &RAM);
    printf("%lu%c\n", RAM, symbol[unit]); // output is 12G
}

0

Używam tego dla Windows (prefiksy binarne):

static readonly string[] BinaryPrefix = { "bytes", "KB", "MB", "GB", "TB" }; // , "PB", "EB", "ZB", "YB"
string GetMemoryString(double bytes)
{
    int counter = 0;
    double value = bytes;
    string text = "";
    do
    {
        text = value.ToString("0.0") + " " + BinaryPrefix[counter];
        value /= 1024;
        counter++;
    }
    while (Math.Floor(value) > 0 && counter < BinaryPrefix.Length);
    return text;
}

0

Włączyłem to (z niewielkimi modyfikacjami) do konwertera UWP DataBinding dla mojego projektu i pomyślałem, że może to być również przydatne dla innych.

Kod to:

using System;
using System.Text;
using Windows.UI.Xaml.Data;

namespace MyApp.Converters
{
    public class ByteSizeConverter : IValueConverter
    {
        static readonly string[] sSizeSuffixes = { "bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" };

        // The number of decimal places the formatter should include in the scaled output - default 1dp
        public int DecimalPlaces { get; set; } = 1;

        public object Convert(object value, Type targetType, object parameter, string language)
        {
            Int64 intVal = System.Convert.ToInt64(value);

            return SizeSuffix(intVal);
        }

        public object ConvertBack(object value, Type targetType, object parameter, string language)
        {
            // TODO: Parse string into number and suffix
            //       Scale number by suffix multiplier to get bytes
            throw new NotImplementedException();
        }

        string SizeSuffix(Int64 value)
        {
            if (this.DecimalPlaces < 0) { throw new ArgumentOutOfRangeException(String.Format("DecimalPlaces = {0}", this.DecimalPlaces)); }
            if (value < 0) { return "-" + SizeSuffix(-value); }
            if (value == 0) { return string.Format("{0:n" + this.DecimalPlaces + "} bytes", 0); }

            // magnitude is 0 for bytes, 1 for KB, 2, for MB, etc.
            int magnitude = (int)Math.Log(value, 1024);
            // clip magnitude - only 8 values currently supported, this prevents out-of-bounds exception
            magnitude = Math.Min(magnitude, 8);

            // 1L << (magnitude * 10) == 2 ^ (10 * magnitude) [i.e. the number of bytes in the unit corresponding to magnitude]
            decimal adjustedSize = (decimal)value / (1L << (magnitude * 10));

            // make adjustment when the value is large enough that it would round up to 1000 or more
            if (Math.Round(adjustedSize, this.DecimalPlaces) >= 1000)
            {
                magnitude += 1;
                adjustedSize /= 1024;
            }

            return String.Format("{0:n" + this.DecimalPlaces + "} {1}", adjustedSize, sSizeSuffixes[magnitude]);
        }
    }
}

Aby z niego skorzystać, dodaj zasób lokalny do UserControl lub Page XAML:

<UserControl.Resources>
    <converters:ByteSizeConverter x:Key="ByteFormat" DecimalPlaces="3" />
</UserControl.Resources>

Odwołaj się do niego w szablonie powiązania danych lub wystąpieniu powiązania danych:

<TextBlock HorizontalAlignment="Left" VerticalAlignment="Center"
    Text="{x:Bind MyItem.FileSize_bytes, Mode=OneWay, Converter={StaticResource ByteFormat}}" />

I hej presto. Dzieje się magia.


Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.