¿Cómo obtengo un tamaño de archivo legible para humanos en abreviatura de bytes usando .NET?


Respuestas:


353

Esta no es la forma más eficiente de hacerlo, pero es más fácil de leer si no está familiarizado con las matemáticas de registro, y debe ser lo suficientemente rápido para la mayoría de los escenarios.

string[] sizes = { "B", "KB", "MB", "GB", "TB" };
double len = new FileInfo(filename).Length;
int order = 0;
while (len >= 1024 && order < sizes.Length - 1) {
    order++;
    len = len/1024;
}

// Adjust the format string to your preferences. For example "{0:0.#}{1}" would
// show a single decimal place, and no space.
string result = String.Format("{0:0.##} {1}", len, sizes[order]);

12
Creo que podría usar Math.Log para determinar el orden en lugar de usar un ciclo while.
Francois Botha el

12
Además, KB es de 1000 bytes. 1024 bytes es KiB .
Constantin

13
@Constantin bien eso depende del sistema operativo? Windows todavía cuenta 1024 bytes como 1 KB y 1 MB = 1024 KB, personalmente quiero tirar el KiB por la ventana y solo contar cada cosa usando 1024? ...
Peter

44
@Petoj no depende del sistema operativo, la definición es independiente del sistema operativo. De Wikipedia:The unit was established by the International Electrotechnical Commission (IEC) in 1998 and has been accepted for use by all major standards organizations
ANeves

3
Prefiero este código, ya que parece ejecutarse más rápido, pero lo modifiqué ligeramente para permitir diferentes números de decimales. Los números más pequeños muestran mejor 2 lugares decimales, por ejemplo, 1,38 MB, mientras que los números más grandes requieren menos decimales, por ejemplo, 246k o 23.5KB:
Myke Black

321

usando Log para resolver el problema ...

static String BytesToString(long byteCount)
{
    string[] suf = { "B", "KB", "MB", "GB", "TB", "PB", "EB" }; //Longs run out around EB
    if (byteCount == 0)
        return "0" + suf[0];
    long bytes = Math.Abs(byteCount);
    int place = Convert.ToInt32(Math.Floor(Math.Log(bytes, 1024)));
    double num = Math.Round(bytes / Math.Pow(1024, place), 1);
    return (Math.Sign(byteCount) * num).ToString() + suf[place];
}

También en c #, pero debería ser fácil de convertir. También redondeé a 1 decimal para facilitar la lectura.

Básicamente, determine el número de lugares decimales en Base 1024 y luego divídalo entre 1024 ^ decimales.

Y algunas muestras de uso y salida:

Console.WriteLine(BytesToString(9223372036854775807));  //Results in 8EB
Console.WriteLine(BytesToString(0));                    //Results in 0B
Console.WriteLine(BytesToString(1024));                 //Results in 1KB
Console.WriteLine(BytesToString(2000000));              //Results in 1.9MB
Console.WriteLine(BytesToString(-9023372036854775807)); //Results in -7.8EB

Editar: Se señaló que me perdí un piso de matemáticas, así que lo incorporé. (Convert.ToInt32 usa redondeo, no truncamiento y es por eso que Floor es necesario). Gracias por la captura.

Edit2: Hubo un par de comentarios sobre tamaños negativos y tamaños de 0 bytes, así que actualicé para manejar esos 2 casos.


77
Quiero advertir que si bien esta respuesta es de hecho un código corto, no es la más optimizada. Me gustaría que eche un vistazo al método publicado por @humbads. Ejecuté microtesting enviando 10 000 000 de tamaños de archivos generados aleatoriamente a través de ambos métodos y esto arroja números de que su método es ~ 30% más rápido. Sin embargo, hice un poco más de limpieza de su método (asignaciones y casting de unnesecary). Además, ejecuté una prueba con un tamaño negativo (cuando está comparando archivos), mientras que el método de humbads procesa sin problemas este método de registro arrojará una excepción.
IvanL

1
Sí, debe agregar Math.Abs ​​para tamaños negativos. Además, el código no maneja el caso si el tamaño es exactamente 0.
dasheddot

Math.Abs, Math.Floor, Math.Log, Convertir a entero, Math.Round, Math.Pow, Math.Sign, Agregar, Multiplicar, Dividir? ¿No eran toneladas de matemáticas solo un gran aumento en el procesador? Esto es probablemente más lento que el código @humbads
Jayson Ragasa

Falla para double.MaxValue(lugar = 102)
BrunoLM

¡Funciona genial! Para imitar la forma en que funciona Windows (al menos en mi Windows 7 ultimate), reemplace Math.Round con Math.Ceiling. Gracias de nuevo. Me gusta esta solución
H_He

101

Aquí se publica una versión probada y significativamente optimizada de la función solicitada:

Tamaño de archivo legible para humanos de C #: función optimizada

Código fuente:

// Returns the human-readable file size for an arbitrary, 64-bit file size 
// The default format is "0.### XB", e.g. "4.2 KB" or "1.434 GB"
public string GetBytesReadable(long i)
{
    // Get absolute value
    long absolute_i = (i < 0 ? -i : i);
    // Determine the suffix and readable value
    string suffix;
    double readable;
    if (absolute_i >= 0x1000000000000000) // Exabyte
    {
        suffix = "EB";
        readable = (i >> 50);
    }
    else if (absolute_i >= 0x4000000000000) // Petabyte
    {
        suffix = "PB";
        readable = (i >> 40);
    }
    else if (absolute_i >= 0x10000000000) // Terabyte
    {
        suffix = "TB";
        readable = (i >> 30);
    }
    else if (absolute_i >= 0x40000000) // Gigabyte
    {
        suffix = "GB";
        readable = (i >> 20);
    }
    else if (absolute_i >= 0x100000) // Megabyte
    {
        suffix = "MB";
        readable = (i >> 10);
    }
    else if (absolute_i >= 0x400) // Kilobyte
    {
        suffix = "KB";
        readable = i;
    }
    else
    {
        return i.ToString("0 B"); // Byte
    }
    // Divide by 1024 to get fractional value
    readable = (readable / 1024);
    // Return formatted number with suffix
    return readable.ToString("0.### ") + suffix;
}

1
+1! Más simple y directo! ¡Hace que el procesador haga los cálculos de manera fácil y rápida!
Jayson Ragasa

Para su información, no utiliza el valor en double readable = (i < 0 ? -i : i);ningún lugar, así que elimínelo. Una cosa más, el elenco es redaundat
Royi Namir

Eliminé el elenco, agregué comentarios y solucioné un problema con el signo negativo.
humbads

Gran respuesta. Gracias, ¿por qué no solo usar Math.Abs?
kspearrin

1
(i <0? -i: i) es aproximadamente un 15% más rápido que Math.Abs. Para un millón de llamadas, Math.Abs ​​es 0.5 milisegundos más lento en mi máquina: 3.2 ms frente a 3.7 ms.
humbads

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


/// <summary>
/// Converts a numeric value into a string that represents the number expressed as a size value in bytes, kilobytes, megabytes, or gigabytes, depending on the size.
/// </summary>
/// <param name="filelength">The numeric value to be converted.</param>
/// <returns>the converted string</returns>
public static string StrFormatByteSize (long filesize) {
     StringBuilder sb = new StringBuilder( 11 );
     StrFormatByteSize( filesize, sb, sb.Capacity );
     return sb.ToString();
}

De: http://www.pinvoke.net/default.aspx/shlwapi/StrFormatByteSize.html


36
Puede que sea un novato, pero usar un cañón gigante como pinvoke para matar a ese pato es un gran mal uso.
Bart

27
¿Es esto lo que usa el explorador? Si es así, entonces magníficamente útil para permitir que las personas coincidan con el tamaño de archivo que les muestra con lo que muestra el explorador.
Andrew Backer

8
Y uno que no reinventa la rueda
Matthew Lock

¿No son 11 caracteres un límite constante y un poco bajo para eso? Quiero decir, otros idiomas pueden usar más caracteres para el acrónimo de tamaño de byte u otros estilos de formato.
Ray

1
@Bart, a los novatos les toma un tiempo aprender la sabiduría en esto: "Debemos olvidarnos de las pequeñas eficiencias, digamos alrededor del 97% del tiempo: la optimización prematura es la raíz de todo mal" ubiquity.acm.org/article.cfm? id = 1513451
Matthew Lock

22

Una forma más de pelarlo, sin ningún tipo de bucles y con soporte de tamaño negativo (tiene sentido para cosas como deltas de tamaño de archivo):

public static class Format
{
    static string[] sizeSuffixes = {
        "B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" };

    public static string ByteSize(long size)
    {
        Debug.Assert(sizeSuffixes.Length > 0);

        const string formatTemplate = "{0}{1:0.#} {2}";

        if (size == 0)
        {
            return string.Format(formatTemplate, null, 0, sizeSuffixes[0]);
        }

        var absSize = Math.Abs((double)size);
        var fpPower = Math.Log(absSize, 1000);
        var intPower = (int)fpPower;
        var iUnit = intPower >= sizeSuffixes.Length
            ? sizeSuffixes.Length - 1
            : intPower;
        var normSize = absSize / Math.Pow(1000, iUnit);

        return string.Format(
            formatTemplate,
            size < 0 ? "-" : null, normSize, sizeSuffixes[iUnit]);
    }
}

Y aquí está el conjunto de pruebas:

[TestFixture] public class ByteSize
{
    [TestCase(0, Result="0 B")]
    [TestCase(1, Result = "1 B")]
    [TestCase(1000, Result = "1 KB")]
    [TestCase(1500000, Result = "1.5 MB")]
    [TestCase(-1000, Result = "-1 KB")]
    [TestCase(int.MaxValue, Result = "2.1 GB")]
    [TestCase(int.MinValue, Result = "-2.1 GB")]
    [TestCase(long.MaxValue, Result = "9.2 EB")]
    [TestCase(long.MinValue, Result = "-9.2 EB")]
    public string Format_byte_size(long size)
    {
        return Format.ByteSize(size);
    }
}

19

Partida de los ByteSize biblioteca. ¡Es System.TimeSpanpara los bytes!

Maneja la conversión y el formato por usted.

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

También hace representación de cadenas y análisis.

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

55
Es tu propia biblioteca, ¿no?
Larsenal

10
No hay vergüenza en una biblioteca útil como esta. :-)
Larsenal

13

Me gusta usar el siguiente método (admite hasta terabytes, que es suficiente para la mayoría de los casos, pero se puede extender fácilmente):

private string GetSizeString(long length)
{
    long B = 0, KB = 1024, MB = KB * 1024, GB = MB * 1024, TB = GB * 1024;
    double size = length;
    string suffix = nameof(B);

    if (length >= TB) {
        size = Math.Round((double)length / TB, 2);
        suffix = nameof(TB);
    }
    else if (length >= GB) {
        size = Math.Round((double)length / GB, 2);
        suffix = nameof(GB);
    }
    else if (length >= MB) {
        size = Math.Round((double)length / MB, 2);
        suffix = nameof(MB);
    }
    else if (length >= KB) {
        size = Math.Round((double)length / KB, 2);
        suffix = nameof(KB);
    }

    return $"{size} {suffix}";
}

Tenga en cuenta que esto está escrito para C # 6.0 (2015), por lo que podría necesitar un poco de edición para versiones anteriores.


11
int size = new FileInfo( filePath ).Length / 1024;
string humanKBSize = string.Format( "{0} KB", size );
string humanMBSize = string.Format( "{0} MB", size / 1024 );
string humanGBSize = string.Format( "{0} GB", size / 1024 / 1024 );

Buena respuesta. Debería haber un problema cuando el tamaño del archivo es demasiado pequeño, en cuyo caso / 1024 devuelve 0. Podría usar un tipo fraccionario y llamar Math.Ceilingo algo así.
nawfal

10

Aquí hay una respuesta concisa que determina la unidad automáticamente.

public static string ToBytesCount(this long bytes)
{
    int unit = 1024;
    string unitStr = "b";
    if (bytes < unit) return string.Format("{0} {1}", bytes, unitStr);
    else unitStr = unitStr.ToUpper();
    int exp = (int)(Math.Log(bytes) / Math.Log(unit));
    return string.Format("{0:##.##} {1}{2}", bytes / Math.Pow(unit, exp), "KMGTPEZY"[exp - 1], unitStr);
}

"b" es para bit, "B" es para Byte y "KMGTPEZY" son respectivamente para kilo, mega, giga, tera, peta, exa, zetta y yotta

Se puede ampliar para tener en cuenta ISO / IEC80000 :

public static string ToBytesCount(this long bytes, bool isISO = true)
{
    int unit = 1024;
    string unitStr = "b";
    if (!isISO) unit = 1000;
    if (bytes < unit) return string.Format("{0} {1}", bytes, unitStr);
    else unitStr = unitStr.ToUpper();
    if (isISO) unitStr = "i" + unitStr;
    int exp = (int)(Math.Log(bytes) / Math.Log(unit));
    return string.Format("{0:##.##} {1}{2}", bytes / Math.Pow(unit, exp), "KMGTPEZY"[exp - 1], unitStr);
}

1
para todos los que se preguntan por qué hay un oKMGTPE posterior: es francés ( byteestá octeten francés). Para cualquier otro idioma, simplemente reemplace el oconb
Max R.

7
string[] suffixes = { "B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" };
int s = 0;
long size = fileInfo.Length;

while (size >= 1024)
{
    s++;
    size /= 1024;
}

string humanReadable = String.Format("{0} {1}", size, suffixes[s]);

Debe verificar: while (size> = 1024 && s <suffixes.Length).
TcKs

no ... un entero con signo de 64 bits no puede ir más allá del ZB ... que representa los números 2 ^ 70.
bobwienholt

77
Entonces, ¿por qué poner en YB?
Configurador

A mí me gusta más esta respuesta, pero todos aquí pusieron soluciones realmente ineficientes, debería usar "shift = size >> 10" shift es mucho más rápido que la división ... y creo que es bueno tener el hay un especificador griego adicional, porque en el futuro cercano, una función DLR posible no necesitaría el "tamaño largo ..." podría estar en una CPU de vector de 128 bits o algo que pueda contener ZB y más grande;)
RandomNickName42

44
Bitshifting fue más eficiente que la división en los días de la codificación C en el metal. ¿Has hecho una prueba de rendimiento en .NET para ver si el bithift realmente es más eficiente? No hace mucho tiempo, miré el estado del intercambio xor y descubrí que en realidad era más lento en .NET en comparación con el uso de una variable temporal.
Pete

7

Si intenta hacer coincidir el tamaño como se muestra en la vista de detalles del Explorador de Windows, este es el código que desea:

[DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
private static extern long StrFormatKBSize(
    long qdw,
    [MarshalAs(UnmanagedType.LPTStr)] StringBuilder pszBuf,
    int cchBuf);

public static string BytesToString(long byteCount)
{
    var sb = new StringBuilder(32);
    StrFormatKBSize(byteCount, sb, sb.Capacity);
    return sb.ToString();
}

Esto no solo coincidirá exactamente con el Explorador, sino que también proporcionará las cadenas traducidas para usted y coincidirá con las diferencias en las versiones de Windows (por ejemplo, en Win10, K = 1000 frente a las versiones anteriores K = 1024).


Este código no se compila, debe especificar dll de qué función proviene. Entonces, el prototipo de la función completa suena así: [DllImport ("shlwapi.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern long StrFormatKBSize (long qdw, [MarshalAs (UnmanagedType.LPTStr)] StringBuilder pszBuf, int cchBuf ); Déjame ser el primero en favorecer esta solución. ¿Por qué reinventar la rueda si la rueda ya se inventó? Este es el enfoque típico de todos los programadores de C #, pero desafortunadamente C # no alcanza todos los objetivos que alcanza C ++.
TarmoPikaro

Y una corrección de error más: Int64.MaxValue alcanza 9,223,372,036,854,775,807, lo que requiere asignar un tamaño de búfer de 25+: lo he redondeado a 32 por si acaso (no 11 como en el código de demostración anterior).
TarmoPikaro

Gracias @TarmoPikaro. Cuando copié de mi código de trabajo, perdí el DllImport. También aumentó el tamaño del búfer según su recomendación. ¡Buena atrapada!
Metalogic

enfoque impresionante
tbhaxor

Esto muestra solo la unidad KB. La idea es mostrar la unidad más grande según el valor.
jstuardo

5

Mezcla de todas las soluciones :-)

    /// <summary>
    /// Converts a numeric value into a string that represents the number expressed as a size value in bytes,
    /// kilobytes, megabytes, or gigabytes, depending on the size.
    /// </summary>
    /// <param name="fileSize">The numeric value to be converted.</param>
    /// <returns>The converted string.</returns>
    public static string FormatByteSize(double fileSize)
    {
        FileSizeUnit unit = FileSizeUnit.B;
        while (fileSize >= 1024 && unit < FileSizeUnit.YB)
        {
            fileSize = fileSize / 1024;
            unit++;
        }
        return string.Format("{0:0.##} {1}", fileSize, unit);
    }

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

public enum FileSizeUnit : byte
{
    B,
    KB,
    MB,
    GB,
    TB,
    PB,
    EB,
    ZB,
    YB
}


3

Como la solución de @ NET3. Use shift en lugar de division para probar el rango de bytes, porque la división requiere más costo de CPU.

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

public static string FormatSize(ulong bytes)
{
    int c = 0;
    for (c = 0; c < UNITS.Length; c++)
    {
        ulong m = (ulong)1 << ((c + 1) * 10);
        if (bytes < m)
            break;
    }

    double n = bytes / (double)((ulong)1 << (c * 10));
    return string.Format("{0:0.##} {1}", n, UNITS[c]);
}


2

¿Qué tal algo de recursión:

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

Entonces lo llamas:

return ReturnSize(size, string.Empty);

Bueno, pero se come CPU
kamalpreet

1

Mis 2 centavos:

  • El prefijo para kilobyte es kB (K minúscula)
  • Dado que estas funciones son para fines de presentación, uno debe proporcionar una cultura, por ejemplo: string.Format(CultureInfo.CurrentCulture, "{0:0.##} {1}", fileSize, unit);
  • Dependiendo del contexto, un kilobyte puede tener 1000 o 1024 bytes . Lo mismo ocurre con MB, GB, etc.

3
Un kilobyte significa 1000 bytes ( wolframalpha.com/input/?i=kilobyte ), no depende del contexto. Es históricamente dependía de contexto, como dice Wikipedia, y era de jure cambió en 1998 y el cambio de facto comenzó alrededor de 2005, cuando terabyte discos duros trajeron a la atención del público. El término para 1024 bytes es kibibyte. El código que los cambia según la cultura está produciendo información incorrecta.
Superbest

1

Un enfoque más, por lo que vale. Me gustó la solución optimizada @humbads mencionada anteriormente, así que he copiado el principio, pero lo he implementado de manera un poco diferente.

Supongo que es discutible si debería ser un método de extensión (ya que no todos los largos son necesariamente tamaños de bytes), pero me gustan, ¡y es en algún lugar donde puedo encontrar el método la próxima vez que lo necesite!

Con respecto a las unidades, no creo que haya dicho nunca 'Kibibyte' o 'Mebibyte' en mi vida, y aunque soy escéptico de tales estándares forzados en lugar de evolucionados, supongo que evitará la confusión a largo plazo .

public static class LongExtensions
{
    private static readonly long[] numberOfBytesInUnit;
    private static readonly Func<long, string>[] bytesToUnitConverters;

    static LongExtensions()
    {
        numberOfBytesInUnit = new long[6]    
        {
            1L << 10,    // Bytes in a Kibibyte
            1L << 20,    // Bytes in a Mebibyte
            1L << 30,    // Bytes in a Gibibyte
            1L << 40,    // Bytes in a Tebibyte
            1L << 50,    // Bytes in a Pebibyte
            1L << 60     // Bytes in a Exbibyte
        };

        // Shift the long (integer) down to 1024 times its number of units, convert to a double (real number), 
        // then divide to get the final number of units (units will be in the range 1 to 1023.999)
        Func<long, int, string> FormatAsProportionOfUnit = (bytes, shift) => (((double)(bytes >> shift)) / 1024).ToString("0.###");

        bytesToUnitConverters = new Func<long,string>[7]
        {
            bytes => bytes.ToString() + " B",
            bytes => FormatAsProportionOfUnit(bytes, 0) + " KiB",
            bytes => FormatAsProportionOfUnit(bytes, 10) + " MiB",
            bytes => FormatAsProportionOfUnit(bytes, 20) + " GiB",
            bytes => FormatAsProportionOfUnit(bytes, 30) + " TiB",
            bytes => FormatAsProportionOfUnit(bytes, 40) + " PiB",
            bytes => FormatAsProportionOfUnit(bytes, 50) + " EiB",
        };
    }

    public static string ToReadableByteSizeString(this long bytes)
    {
        if (bytes < 0)
            return "-" + Math.Abs(bytes).ToReadableByteSizeString();

        int counter = 0;
        while (counter < numberOfBytesInUnit.Length)
        {
            if (bytes < numberOfBytesInUnit[counter])
                return bytesToUnitConverters[counter](bytes);
            counter++;
        }
        return bytesToUnitConverters[counter](bytes);
    }
}

0

Utilizo el método de extensión larga a continuación para convertir a una cadena de tamaño legible por humanos. Este método es la implementación en C # de la solución Java de esta misma pregunta publicada en Stack Overflow, aquí .

/// <summary>
/// Convert a byte count into a human readable size string.
/// </summary>
/// <param name="bytes">The byte count.</param>
/// <param name="si">Whether or not to use SI units.</param>
/// <returns>A human readable size string.</returns>
public static string ToHumanReadableByteCount(
    this long bytes
    , bool si
)
{
    var unit = si
        ? 1000
        : 1024;

    if (bytes < unit)
    {
        return $"{bytes} B";
    }

    var exp = (int) (Math.Log(bytes) / Math.Log(unit));

    return $"{bytes / Math.Pow(unit, exp):F2} " +
           $"{(si ? "kMGTPE" : "KMGTPE")[exp - 1] + (si ? string.Empty : "i")}B";
}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.