2008-11-11 7 views
216

Comment puis-je obtenir une taille de fichier lisible par l'homme en abréviations d'octets en utilisant .NET?Comment puis-je obtenir une taille de fichier lisible par un humain en abréviations d'octets en utilisant .NET?

Exemple: Prenez entrée 7.326.629 et afficher 6,98 MB

+0

Qu'est-ce à propos http://stackoverflow.com/questions/128618/c-file-size -format-fournisseur? – Kiquenet

+0

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

Répondre

274

Ce n'est pas la façon la plus efficace de le faire, mais il est plus facile à lire si vous n'êtes pas familier avec les mathématiques journaux et devrait être assez rapide pour la plupart des scénarios.

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

Je pense que "len> = 1024 && ..." sera mieux, mais c'est un détail. – TcKs

+0

C'est vrai, merci. –

+1

C'est exactement ce que je ferais ... Sauf que j'utiliserais "{0: 0. #} {1}" comme chaîne de format ... Il n'y a généralement pas vraiment besoin de deux chiffres après le point et je ne fais pas Je n'aime pas y mettre un espace. Mais c'est juste moi. – 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

Bonne réponse. Il devrait y avoir un problème lorsque la taille du fichier est trop petite, dans ce cas/1024 renvoie 0. Vous pouvez utiliser un type fractionnaire et appeler 'Math.Ceiling' ou quelque chose. – nawfal

2

Je suppose que vous êtes à la recherche de "1,4 MB" au lieu de "1468006 octets"?

Je ne pense pas qu'il existe une manière intégrée de faire cela dans .NET. Vous devrez juste déterminer quelle unité est appropriée et la formater.

Edit: Voici quelques exemples de code pour le faire que:

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

Vous devriez vérifier: while (size> = 1024 && s TcKs

+0

non ... un entier signé de 64 bits ne peut pas dépasser le ZB ... qui représente les nombres 2^70. – bobwienholt

+7

Alors pourquoi mettre 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(); 
} 

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

+26

Je pourrais être un noob, mais en utilisant un canon gigantesque comme pinvoke pour tuer ce canard est un gros abus. – Bart

+21

Est-ce ce que l'explorateur utilise? Si c'est le cas, alors magnifiquement utile pour permettre aux gens de correspondre à la taille du fichier que vous leur montrez avec ce que montre l'explorateur. –

+4

Et celui qui ne réinvente pas la roue –

17

Une autre façon de la peau, sans aucune sorte de boucles et avec le soutien de taille négative (logique pour des choses comme les deltas de taille de fichier):

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

Et voici la suite de tests:

[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

en utilisant le journal pour résoudre le problème ....

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

également en C#, mais devrait être un clin d'oeil à convertir. Aussi j'ai arrondi à 1 décimale pour la lisibilité.

Fondamentalement, déterminez le nombre de décimales dans la base 1024, puis divisez par 1024^décimales.

Et quelques exemples d'utilisation et de la production:

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 

Edit: a fait remarquer que j'ai manqué un Math.floor, donc je l'intégra. (Convert.ToInt32 utilise l'arrondi, pas tronqué et c'est pourquoi Floor est nécessaire.) Merci pour la capture.

Edit2: Il y avait quelques commentaires sur les tailles négatives et les tailles de 0 octets, donc j'ai mis à jour pour gérer ces 2 cas.

+7

Je tiens à avertir que si cette réponse est en effet un court morceau de code, il n'est pas le plus optimisé. J'aimerais que vous jetiez un coup d'oeil à la méthode publiée par @humbads. J'ai couru microtesting en envoyant 10 000 000 filesized générés aléatoirement par les deux méthodes et ceci amène des nombres que sa méthode est ~ 30% plus rapide. J'ai fait un peu plus de nettoyage de sa méthode (assignations et tournages non rémunérés). De plus j'ai effectué un test avec une taille négative (quand vous comparez des fichiers) alors que la méthode des humbads traite parfaitement ceci, cette méthode de log va lancer une exception! – IvanL

+1

Oui, vous devriez ajouter Math.Abs ​​pour les tailles négatives. De plus le code ne gère pas le cas si la taille est exactement 0. – dasheddot

+0

Math.Abs, Math.Floor, Math.Log, Convertir en nombre entier, Math.Round, Math.Pow, Math.Sign, Ajouter, Multiplier, Diviser? N'était-ce pas des tonnes de maths qui faisaient un énorme pic sur le processeur. Ceci est probablement plus lent que le code @humbads –

4

Mélange de toutes les solutions :-)

/// <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

A testé et la version optimisée de manière significative de la fonction demandée est affichée ici:

C# Human Readable File Size - Optimized Function

code source:

// 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! Plus simple et direct! Permet au processeur de faire les calculs facilement et rapidement! –

+0

FYI, vous n'utilisez pas la valeur dans 'double readable = (i <0? -i: i);' n'importe où, alors supprimez-le. encore une chose, la distribution est redaundat –

+0

J'ai enlevé la distribution, ajouté des commentaires, et a corrigé un problème avec le signe négatif. – humbads

1

Mes 2 cents:

  • Le préfixe kilo-octet est kB (minuscule K)
  • Étant donné que ces fonctions sont à des fins de présentation, il faut fournir une culture, par exemple: string.Format(CultureInfo.CurrentCulture, "{0:0.##} {1}", fileSize, unit);
  • Selon l'un contexte kilobyte peut être soit 1000 or 1024 bytes. La même chose vaut pour MB, GB, etc.
+3

Un kilo-octet signifie 1000 octets (http://www.wolframalpha.com/input/?i=kilobyte), cela ne dépend pas du contexte. Il dépend historiquement du contexte, comme le dit wikipedia, et il a été modifié de jure en 1998 et les changements de facto ont commencé autour de 2005 lorsque les disques durs de téraoctets ont attiré l'attention du public. Le terme pour 1024 octets est kibibyte. Le code qui les commute en fonction de la culture produit des informations incorrectes. – Superbest

1

Une approche de plus, pour ce que ça vaut. J'ai aimé @humbads solution optimisée référencé ci-dessus, donc j'ai copié le principe, mais je l'ai mis en œuvre un peu différemment. Je suppose qu'il est discutable de savoir si ce devrait être une méthode d'extension (puisque tous les longs ne sont pas nécessairement des tailles d'octets), mais je les aime, et c'est quelque part que je peux trouver la méthode quand j'en ai besoin! En ce qui concerne les unités, je ne pense pas avoir jamais dit «Kibibyte» ou «Mebibyte» dans ma vie, et même si je suis sceptique à l'égard de ces normes appliquées plutôt qu'évolues, je suppose que cela évitera la confusion. à long terme.

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

Extrayez la bibliothèque ByteSize. C'est le System.TimeSpan pour les octets!

Il gère la conversion et le formatage pour vous.

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

Il effectue également une représentation de chaîne et une analyse syntaxique.

// 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

C'est votre propre bibliothèque, non? – Larsenal

+0

Oui. Bouchon sans vergogne? – Omar

+4

Pas de honte dans une bibliothèque pratique comme celle-ci. :-) – Larsenal

3

Il existe un projet open source qui peut faire cela et bien plus encore.

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

J'aime utiliser la méthode suivante (il prend en charge jusqu'à téraoctets, ce qui est suffisant pour la plupart des cas, mais il peut facilement être étendu):

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

Veuillez garder à l'esprit que ceci est écrit pour C# 6.0 (2015), donc il pourrait avoir besoin d'un peu d'édition pour les versions antérieures.

5

Si vous essayez de faire correspondre la taille comme indiqué dans la vue détaillée de l'Explorateur Windows, c'est le code que vous voulez:

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

Cela permettra non seulement Explorer correspondre exactement, mais fournira également les chaînes traduites pour vous et correspondent aux différences dans les versions de Windows (par exemple dans Win10, K = 1000 par rapport aux versions précédentes K = 1024).

+0

Ce code ne compile pas, vous devez spécifier dll d'où provient la fonction. Donc le prototype de la fonction entière ressemble à ceci: [DllImport ("shlwapi.dll", CharSet = CharSet.Auto, SetLastError = true)] public static externe long StrFormatKBSize (long qdw, [MarshalAs (UnmanagedType.LPTStr)] StringBuilder pszBuf, int cchBuf); Laissez-moi être le premier à privilégier cette solution. Pourquoi réinventer la roue si la roue était déjà inventée? C'est une approche typique de tous les programmeurs C#, mais malheureusement C# n'atteint pas toutes les cibles que C++ atteint. – TarmoPikaro

+0

Et encore un bugfix: Int64.MaxValue atteint 9,223,372,036,854,775,807, ce qui nécessite d'allouer une taille de tampon de 25+ - Je l'ai arrondi à 32 juste au cas où (pas 11 comme dans le code de démo ci-dessus). – TarmoPikaro

+0

Merci @TarmoPikaro. Quand j'ai copié de mon code de travail j'ai raté le DllImport. Également augmenté la taille du tampon selon votre recommandation. Bonne prise! – Metalogic

1

Comme la solution @ NET3. Utilisez shift au lieu de division pour tester la plage de bytes, car la division prend plus de coût 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

Que diriez-vous récursion:

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

Alors vous l'appelez:

return ReturnSize(size, string.Empty); 
Questions connexes