.NET fornisce un modo semplice per convertire i byte in KB, MB, GB, ecc.?


112

Mi chiedo solo se .NET fornisce un modo pulito per farlo:

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

eccetera...

Risposte:


192

Ecco un modo abbastanza conciso per farlo:

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]);
}

Ed ecco l'implementazione originale che ho suggerito, che potrebbe essere leggermente più lenta, ma un po 'più facile da seguire:

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));

Una cosa da tenere a mente: nella notazione SI, "chilo" di solito usa una k minuscola mentre tutte le unità più grandi usano una lettera maiuscola. Windows utilizza KB, MB, GB, quindi ho usato KB sopra, ma potresti invece considerare kB.


Il richiedente cerca solo 1 cifra decimale di precisione. Potresti fornire un esempio di un input che produce un output errato?
JLRishe

2
Entrambi gli esempi ora usano la divisione in virgola mobile, quindi dovrebbe esserci molta meno preoccupazione per gli errori di arrotondamento.
JLRishe

Grazie, proprio quello che stavo cercando. (2a implementazione.)
snapplex

1
Implementazione molto accurata. Nota che se passi il valore 0 a questa funzione, verrà generata un'eccezione IndexOutOfRangeException. Ho deciso di aggiungere un if (value == 0) { return "0"; }segno di spunta all'interno della funzione.
bounav

Potete fornire il caso in cui la dimensione del file è <0? Per me sembra strano ...
Ruslan F.

84

Checkout la ByteSize biblioteca. È il System.TimeSpanper byte!

Gestisce la conversione e la formattazione per te.

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

Inoltre esegue la rappresentazione e l'analisi delle stringhe.

// 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
Semplice da usare e da capire, funziona con .Net 4.0 e versioni successive.
The Joker

34
Questo dovrebbe essere incluso come parte del framework .NET
helios456

L'unico problema che vedo è che i metodi di conversione funzionano solo da non byte a byte, ma non viceversa.
SuperJMN

@SuperJMN cosa intendi per non byte? Ti piacciono i bit? C'è un metodo .FromBits che puoi usare.
Omar

1
Se i tuoi dati di origine sono qualcosa di diverso da "byte" e devi essere in grado di convertirli in qualsiasi cosa ... questa è la libreria che dovresti usare.
James Blake

37

Dato che tutti gli altri pubblicano i loro metodi, ho pensato di pubblicare il metodo di estensione che di solito uso per questo:

EDIT: aggiunte varianti int / long ... e corretto un refuso 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;
    }
}

Tieni presente, tuttavia, che la b minuscola può in genere indicare bit anziché byte. :-) en.wikipedia.org/wiki/Data-rate_units#Kilobit_per_second
SharpC

32

Lo risolverei usando Extension methods, Math.Powfunction e 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");
    }
}

e usalo come:

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

3
Soluzione elegante!
yossico

1
Ho usato la tua idea per crearne una che determini automaticamente l'unità. +1
Louis Somers,

2
È una soluzione molto elegante, molto più pulita e consona alla soluzione approvata. Tuttavia, in senso stretto, basato sui valori enum, dovrebbe essere basato sulla potenza di 1000, cioè non 1024 ( en.wikipedia.org/wiki/Terabyte ) codice ... stringa statica pubblica ToSize (questo valore lungo, unità di unità) => $ "{value / Math.Pow (1000, (long) unit): F2} {unit.ToString ()}";
stoj

6

La versione breve della risposta più votata ha problemi con i valori TB.

L'ho regolato in modo appropriato per gestire anche i valori tb e ancora senza un ciclo e ho anche aggiunto un piccolo controllo degli errori per i valori negativi. Ecco la mia soluzione:

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
Il problema dichiarato con valori elevati non dovrebbe più essere presente nella risposta accettata.
JLRishe

5

No. Principalmente perché si tratta di un'esigenza piuttosto di nicchia e ci sono troppe varianti possibili. (È "KB", "Kb" o "Ko"? È un megabyte di 1024 * 1024 byte o 1024 * 1000 byte? - sì, alcuni posti lo usano!)


1
+1 - secondo Wikipedia , kb => 1000 byte e KiB => 1024 byte.
Peter Majeed

5

Ecco un'opzione che è più facile da estendere della tua, ma no, non ce n'è nessuna integrata nella libreria stessa.

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

Questo è un modo per farlo anche (il numero 1073741824.0 è da 1024 * 1024 * 1024 aka GB)


3

La risposta di @ Servy è stata gentile e concisa. Penso che possa essere ancora più semplice?

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

Basato sull'elegante soluzione di 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)
}

Forse ci sono commenti eccessivi, ma tendo a lasciarli per evitare di commettere gli stessi errori nelle visite future ...



1

Ho combinato alcune delle risposte qui in due metodi che funzionano alla grande. Il secondo metodo di seguito convertirà da una stringa di byte (come 1.5.1 GB) in byte (come 1621350140) come valore di tipo lungo. Spero che questo sia utile ad altri che cercano una soluzione per convertire i byte in una stringa e di nuovo in byte.

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

Posso chiederle perché si usa float.Parseper double?
John_J

1

So che questo è già un vecchio thread. ma forse qualcuno cercherà una soluzione. Ed ecco cosa uso e il modo più semplice

  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

Che ne dite di:

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

Ad esempio, chiama come

printMB(123456);

Risulterà in output

"Size is 120,56 MB"

0

Ho optato per la soluzione JerKimballs e mi piace. Tuttavia, vorrei aggiungere / sottolineare che questa è davvero una questione di controversia nel suo complesso. Nella mia ricerca (per altri motivi) ho trovato le seguenti informazioni.

Quando le persone normali (ho sentito che esistono) parlano di gigabyte si riferiscono al sistema metrico in cui 1000 alla potenza di 3 dal numero originale di byte == il numero di gigabyte. Tuttavia, ovviamente ci sono gli standard IEC / JEDEC che sono ben riassunti in wikipedia, che invece di 1000 alla potenza di x hanno 1024. Che per dispositivi di archiviazione fisici (e immagino logico come amazon e altri) significa un differenza sempre crescente tra metrica e IEC. Quindi, per esempio 1 TB == 1 terabyte metrico è 1000 alla potenza di 4, ma IEC definisce ufficialmente il numero simile come 1 TiB, tebibyte come 1024 alla potenza di 4. Ma, ahimè, in applicazioni non tecniche (lo farei vai dal pubblico) la norma è metrica e nella mia app per uso interno attualmente spiego la differenza nella documentazione. Ma per scopi di visualizzazione non offro nemmeno altro che metrica. Internamente, anche se non è rilevante nella mia app, memorizzo solo i byte ed eseguo il calcolo per la visualizzazione.

Come nota a margine trovo un po 'poco brillante che il framework .Net AFAIK (e spesso sbaglio grazie ai poteri che sono) anche nella sua incarnazione 4.5 non contiene nulla al riguardo in nessuna libreria interna. Ci si aspetterebbe che una libreria open source di qualche tipo a un certo punto sia NuGettabile, ma ammetto che questo è un piccolo problema. D'altra parte System.IO.DriveInfo e altri hanno anche solo byte (purché lunghi), il che è piuttosto chiaro.


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

Che ne dici di una ricorsione:

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");
  }
}

Quindi puoi chiamarlo:

ReturnSize(size, string.Empty);

0

Come postato sopra, la ricorsione è il modo preferito, con l'aiuto del logaritmo.

La seguente funzione ha 3 argomenti: l'input, il vincolo dimensionale dell'output, ovvero il terzo argomento.

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

Ora convertiamo 12 GB di RAM in più unità:

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

Lo uso per Windows (prefissi binari):

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

L'ho incorporato (con poche o nessuna modifica) in un convertitore DataBinding UWP per il mio progetto e ho pensato che potesse essere utile anche ad altri.

Il codice è:

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]);
        }
    }
}

Per usarlo, aggiungi una risorsa locale a UserControl o Page XAML:

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

Fare riferimento a un modello di associazione dati o un'istanza di associazione dati:

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

E presto. La magia accade.


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.