2008-11-11 11 views
216

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

Ejemplo: tomar la entrada y mostrar 7.326.629 6,98 MB

+0

¿Cuál es sobre http://stackoverflow.com/questions/128618/c-file-size ¿formateador? – Kiquenet

+0

Y http://stackoverflow.com/questions/14488796/does-net-provide-an-easy-way-convert-bytes-to-kb-mb-gb-etc .... – vapcguy

Respuesta

274

Ésta no es la manera 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]); 
+0

Creo que "len> = 1024 && ..." será mejor, pero es un detalle. – TcKs

+0

Así es, gracias. –

+1

Esto es exactamente lo que haría ... Excepto que usaría "{0: 0. #} {1}" como la cadena de formato ... Generalmente no hay una necesidad real de dos dígitos después del punto y no lo hago Me gusta poner un espacio allí. Pero solo soy yo. – configurator

10
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); 
+0

Buena respuesta. Debería haber un problema cuando el tamaño del archivo es demasiado pequeño, en cuyo caso 1024 devuelve 0. Puede usar un tipo fraccional y llamar a 'Math.Ceiling' o algo así. – nawfal

2

supongo que estás buscando "1.4 MB" en lugar de "1468006 bytes"?

No creo que haya una forma integrada de hacerlo en .NET. Tendrá que averiguar qué unidad es la adecuada y formatearla.

Editar: Aquí hay algunos ejemplos de código para hacer precisamente eso:

http://www.codeproject.com/KB/cpp/formatsize.aspx

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]); 
+0

Debe verificar: while (tamaño> = 1024 && s TcKs

+0

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

+7

¿Por qué poner en YB? – configurator

68
[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(); 
} 

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

+26

Podría ser un novato, pero usar un cañón tan gigantesco como pinvoke para matar a ese pato es un gran mal uso. – Bart

+21

¿Es esto lo que usa el explorador? Si es así, es magníficamente útil para permitir que las personas coincidan con el tamaño del archivo que se muestra con lo que muestra el explorador. –

+4

Y uno que no reinventa la rueda –

17

una forma más de la piel que, sin ningún tipo de bucles y con el apoyo de tamaño negativo (que tiene sentido para las cosas como los deltas 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); 
    } 
} 
282

usando registro 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 debe ser un complemento para convertir. También redondeé a 1 lugar decimal para la legibilidad.

Básicamente Determine el número de lugares decimales en la Base 1024 y luego divida por 1024^decimales.

Y algunos ejemplos de uso y de 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 echaba de menos un Math.floor, por lo que la incorporaron. (Convert.ToInt32 usa redondeo, no trunca 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, por lo que actualicé para manejar esos 2 casos.

+7

Quiero advertir que si bien esta respuesta es de hecho una pequeña porción de código, no es la más optimizada. Me gustaría que eches un vistazo al método publicado por @humbads. Ejecuté microtesting enviando 10 000 000 filesizes generados aleatoriamente a través de ambos métodos y esto muestra que su método es ~ 30% más rápido. Sin embargo, hice un poco más de limpieza de su método (asignaciones únicas y casting). Además ejecuté una prueba con un tamaño negativo (cuando comparas archivos) mientras que el método de humbads procesa perfectamente este este método de registro lanzará 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

+0

Math.Abs, Math.Floor, Math.Log, Converting to integer, Math.Round, Math.Pow, Math.Sign, Adding, Multiplying, Dividing? ¿No fueron estas toneladas de matemáticas las que hicieron un gran aumento en el procesador? Probablemente esto sea más lento que @humbads code –

4

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

Un probado y versión optimizada de manera significativa de la función solicitada se publicarán aquí: Código

C# Human Readable File Size - Optimized Function

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! ¡Simple y directo! ¡Hace que el procesador haga los cálculos de manera fácil y rápida! –

+0

FYI, no utiliza el valor en 'doble legible = (i <0? -i: i);' en cualquier lugar, así que elimínelo. una cosa más, el elenco es redaundat –

+0

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

1

Mis 2 centavos:

  • El prefijo para kilobyte es kB (minúscula K)
  • Puesto que estas funciones son para fines de presentación, se debe suministrar una cultura, por ejemplo: string.Format(CultureInfo.CurrentCulture, "{0:0.##} {1}", fileSize, unit);
  • Dependiendo del contexto una kilobyte puede ser 1000 or 1024 bytes. Lo mismo vale para MB, GB, etc.
+3

Un kilobyte significa 1000 bytes (http://www.wolframalpha.com/input/?i=kilobyte), no depende del contexto. Históricamente * dependía del contexto, como dice wikipedia, y fue cambiado de jure en 1998 y el cambio de facto comenzó alrededor de 2005 cuando los discos duros de Terabyte llamaron 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 la pena. Me gustó la solución optimizada de @humbads mencionada anteriormente, así que he copiado el principio, pero lo he implementado de forma un poco diferente.

Supongo que es discutible si debe ser un método de extensión (ya que no todas las longitudes son necesariamente tamaños de byte), pero me gustan, ¡y es un lugar en el que puedo encontrar el método la próxima vez que lo necesite!

En cuanto a las unidades, no creo que alguna vez haya dicho 'Kibibyte' o 'Mebibyte' en mi vida, y aunque soy escéptico de tales normas impuestas en lugar de evolucionadas, supongo que evitará confusiones 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); 
    } 
} 
9

Pagar la biblioteca ByteSize. ¡Es el System.TimeSpan para bytes!

Gestiona la conversión y el formateo por usted.

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

También realiza 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"); 
+2

Es tu propia biblioteca, ¿no? – Larsenal

+0

Sí. ¿Un enchufe desvergonzado? – Omar

+4

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

3

Hay un proyecto de código abierto que puede hacer eso y mucho más.

7.Bits().ToString();   // 7 b 
8.Bits().ToString();   // 1 B 
(.5).Kilobytes().Humanize(); // 512 B 
(1000).Kilobytes().ToString(); // 1000 KB 
(1024).Kilobytes().Humanize(); // 1 MB 
(.5).Gigabytes().Humanize(); // 512 MB 
(1024).Gigabytes().ToString(); // 1 TB 

http://humanizr.net/#bytesize

https://github.com/MehdiK/Humanizer

11

me gusta usar el siguiente método (que soporta hasta terabytes, que es suficiente para la mayoría de los casos, pero puede ser fácilmente extendido):

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 una pequeña edición para versiones anteriores.

5

Si usted está tratando de coincidir con el tamaño que se muestra en la vista de detalle del Explorador de Windows, este es el código que desee:

[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 sólo Explorador partido exactamente sino que también proporcionará las cadenas traducidas para usted y las diferencias de coincidencia en las versiones de Windows (por ejemplo, en Win10, K = 1000 frente a las versiones anteriores K = 1024).

+0

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

+0

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

+0

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

1

Me gusta Solución de @ NET3. Usa shift en lugar de división 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]); 
} 
1

tal un poco de recursividad:

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 llaman:

return ReturnSize(size, string.Empty); 
Cuestiones relacionadas