Як я можу зрозуміти людський розмір файлу у абревіатух байтів за допомогою .NET?


279

Як я можу зрозуміти людський розмір файлу у абревіатух байтів за допомогою .NET?

Приклад : візьміть вхід 7,326,629 і відобразіть 6,98 Мб



Відповіді:


353

Це не найефективніший спосіб зробити це, але його легше читати, якщо ви не знайомі з математикою журналів, і має бути досить швидким для більшості сценаріїв.

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
Я вважаю, що ви можете використовувати Math.Log для визначення порядку, а не циклу.
Франсуа Бота

12
Також КБ - 1000 байт. 1024 байт - KiB .
Константин

13
@Constantin добре, що залежить від ОС? Windows все ще налічує 1024 байти як 1 Кб і 1 МБ = 1024 Кб, особисто я хочу кинути KiB у вікно і просто порахувати кожну річ, використовуючи 1024? ...
Петро

4
@Petoj це не залежить від ОС, визначення - ОС-агностик. З Вікіпедії: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
Я вважаю за краще цей код, оскільки він, здається, працює швидше, але я його трохи змінив, щоб забезпечити різну кількість десяткових знаків. Менші цифри краще відображати два знаки після коми, наприклад, 1,38 МБ, тоді як для більших цифр потрібно менше десяткових знаків, наприклад, 246 к або 23,5 КБ:
Майк Чорний,

321

за допомогою журналу для вирішення проблеми ....

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

Також в c #, але слід конвертувати просто. Також я округлив до 1 десяткового знаку для читабельності.

В основному визначте кількість десяткових знаків у Базі 1024, а потім розділіть на 1024 ^ десяткових знаків.

І деякі зразки використання та випуску:

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

Правка: Вказувалося, що я пропустив математичний поверх, тому я включив його. (Convert.ToInt32 використовує округлення, а не обрізку, і тому Floor необхідний.) Дякую за улов.

Edit2: Було кілька коментарів щодо негативних розмірів та 0 байтових розмірів, тому я оновив обробку цих двох випадків.


7
Хочу попередити, що хоча ця відповідь справді є коротким кодом, вона не є найбільш оптимізованою. Я хотів би, щоб ви подивилися на метод, розміщений @humbads. Я провів мікротестування, надсилаючи 10 000 000 випадково згенерованих файлів за допомогою обох методів, і це призводить до того, що його метод на 30% швидший. Однак я провів деяку подальшу очистку його методу (неприховані завдання та кастинг). Крім того, я провів тест з негативним розміром (коли ви порівнюєте файли), тоді як метод humbads бездоганно обробляє цей метод журналу, викине виняток!
IvanL

1
Так, слід додати Math.Abs ​​для негативних розмірів. Більше того, код не розглядає випадок, якщо розмір становить рівно 0.
dasheddot

Math.Abs, Math.Floor, Math.Log, Перетворення на ціле число, Math.Round, Math.Pow, Math.Sign, додавання, множення, ділення? Чи не були ці тонни математики просто величезним шипом на процесорі. Це, мабуть, повільніше, ніж код @humbads
Jayson Ragasa

Невдача double.MaxValue(місце = 102)
BrunoLM

Чудово працює! Щоб імітувати спосіб роботи Windows (принаймні, на моєму Windows 7 ultimate), замініть Math.Round на Math.Ceiling. Знову дякую. Мені подобається це рішення.
H_He

101

Тут вивірена перевірена та значно оптимізована версія запитуваної функції:

C # Розбір файлу, доступного для людини - оптимізована функція

Вихідний код:

// 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! Простіше і прямо вперед! Змушує процесор робити математику легко і швидше!
Джейсон Рагаса

FYI, ви не використовуєте значення double readable = (i < 0 ? -i : i);ніде, тому видаліть його. ще одне, акторський склад
редаунд

Я зняв акторський склад, додав коментарі та виправив проблему з негативним знаком.
хабади

Чудова відповідь. Дякую, чому б не просто використовувати Math.Abs?
kspearrin

1
(i <0? -i: i) приблизно на 15% швидше, ніж Math.Abs. На один мільйон дзвінків Math.Abs ​​на моїй машині повільніше на 0,5 мілісекунди - 3,2 мс проти 3,7 мс.
хабади

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

Від: http://www.pinvoke.net/default.aspx/shlwapi/StrFormatByteSize.html


36
Я міг би бути нобієм, але використання таких гігантських гармат, як пінвоке для вбивства цієї качки, є великим зловживанням.
Барт

27
Це те, що використовує Провідник? Якщо так, то надзвичайно корисно для того, щоб дозволити людям відповідати розміру файлу, який ви показуєте їм, і тому, що показує Explorer.
Ендрю Бекер

8
І той, хто не винаходить колесо
Меттью Лок

Хіба 11 символів не є постійною межею і трохи низькою для цього? Тобто, інші мови можуть використовувати більше символів для абревіатури розміру байтів або інших стилів форматування.
Рей

1
@Bart, щоб нооби навчилися мудрості в цьому: "Ми повинні забути про малу ефективність, скажімо, про 97% часу: передчасна оптимізація - корінь усього зла" ubiquity.acm.org/article.cfm? id = 1513451
Блок Метью

22

Ще один спосіб його усунути, без будь-яких циклів і з підтримкою негативного розміру (має сенс для таких речей, як дельти розміру файлу):

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

І ось набір тестів:

[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

Оформити замовлення ByteSize бібліотеку. Це System.TimeSpanбайти!

Він обробляє перетворення та форматування для вас.

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

Він також здійснює подання рядків і їх аналіз.

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

5
Це ваша власна бібліотека, ні?
Ларсенал

10
Без сорому в такій зручній бібліотеці. :-)
Ларсенал

13

Мені подобається використовувати наступний метод (він підтримує до терабайт, чого достатньо для більшості випадків, але його можна легко розширити):

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

Зауважте, що це написано для C # 6.0 (2015), тому для попередніх версій може знадобитися невелике редагування.


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

Хороша відповідь. Виникає проблема, коли розмір файлу занадто малий, і в цьому випадку / 1024 повертає 0. Ви можете використовувати тип дробу та дзвінок Math.Ceilingчи щось.
nawfal

10

Ось стисла відповідь, яка визначає одиницю автоматично.

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" - це біт, "B" - це байт, "KMGTPEZY" - відповідно кіло, мега, гіга, тера, пета, екза, зетта і йотта

Можна розширити його, щоб врахувати 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
для всіх, хто цікавиться, чому виникає oпісля KMGTPE: Її французька ( byteє octetфранцузькою). Для будь-якої іншої мови просто замініть oнаb
Макс Р.

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

Слід перевірити: while (розмір> = 1024 && s <суфікси. Довжина).
TcKs

nope ... 64-бітове ціле число не може вийти за межі ZB ..., що представляє числа 2 ^ 70.
bobwienholt

7
То навіщо ставити в YB?
конфігуратор

Мені найбільше подобається ця відповідь, але всі, хто насправді реалізує справді неефективні рішення, вам слід використовувати зміну "size = size >> 10" - це набагато швидше, ніж поділ ... і я думаю, що добре мати додаткові грецькі специфікатори є, тому що найближчим часом доступній функції DLR не знадобиться "довгий розмір .." Ви можете бути на 128-бітовому векторному процесорі або щось, що може вмістити ZB і більше;)
RandomNickName42

4
Бітшифтінг був більш ефективним, ніж ділення в дні кодування С на металі. Ви зробили тест на Perf в .NET, щоб перевірити, чи дійсно бітшіфтінг є більш ефективним? Не так давно я подивився на стан xor-swap і виявив, що він фактично повільніше в .NET проти використання temp змінної.
Піт

7

Якщо ви намагаєтесь відповідати розміру, як показано в детальному перегляді Windows Explorer, це потрібний вам код:

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

Це не тільки точно відповідатиме Explorer, але й надасть рядки, перекладені для вас, та відповідатимуть відмінностям у версіях Windows (наприклад, у Win10, K = 1000 проти попередніх версій K = 1024).


Цей код не компілюється, вам потрібно вказати dll, з якого походить функція. Так весь прототип функції звучить так: [DllImport ("shlwapi.dll", CharSet = CharSet.Auto, SetLastError = вірно)] загальнодоступний статичний зовнішній довгий StrFormatKBSize (long qdw, [MarshalAs (UnmanagedType.LPTStr)] StringBuildt pszBuf, in. ); Дозвольте мені бути першим, хто підтримає це рішення. Навіщо винаходити колесо, якщо колесо вже винайдено? Це типовий підхід для всіх програмістів на C #, але, на жаль, C # не досягає всіх цілей, яких досягає C ++.
ТармоПікаро

І ще одна помилка: Int64.MaxValue досягає 9,223,372,036,854,775,807, для чого потрібно виділити розмір буфера 25+ - я округлював його до 32 на всякий випадок (не 11, як у демо-коді вище).
ТармоПікаро

Дякую @TarmoPikaro. Коли я скопіював зі свого робочого коду, я пропустив DllImport. Також збільшили розмір буфера відповідно до вашої рекомендації. Гарний улов!
Metalogic

вражаючий підхід
tbhaxor

Це показує лише одиницю KB. Ідея полягає в тому, щоб показати найбільшу одиницю залежно від значення.
jstuardo

5

Суміш усіх розчинів :-)

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

Як і рішення @ NET3. Використовуйте shift замість ділення для тестування діапазону bytes, оскільки поділ вимагає більшої вартості процесора.

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

Я припускаю, що ви шукаєте "1,4 Мб" замість "1468006 байт"?

Я не думаю, що існує вбудований спосіб зробити це в .NET. Вам потрібно буде просто розібратися, який пристрій підходить, і відформатувати його.

Редагувати: Ось зразок коду, щоб зробити саме це:

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


2

Як щодо рекурсії:

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

Тоді ви називаєте це:

return ReturnSize(size, string.Empty);

Добре, але їсть процесор
kamalpreet

1

Мої 2 копійки:

  • Префікс для кілобайт - кБ (малий К)
  • Оскільки ці функції призначені для презентації, слід поставити культуру, наприклад: string.Format(CultureInfo.CurrentCulture, "{0:0.##} {1}", fileSize, unit);
  • Залежно від контексту кілобайт може становити 1000 або 1024 байт . Те саме стосується MB, GB та ін.

3
Кілобайт означає 1000 байт ( wolframalpha.com/input/?i=kilobyte ), це не залежить від контексту. Це історично залежить від контексту, як каже вікіпедія, і це було де - юре змінилося в 1998 році , і де - факто зміна почалося приблизно в 2005 році , коли терабайт жорстких дисків принесли його до відома громадськості. Термін на 1024 байти - кібібайт. Код, який перемикає їх на основі культури, видає неправильну інформацію.
Супербест

1

Ще один підхід, для чого це варто. Мені сподобалось @humbads оптимізоване рішення, на яке згадувалося вище, тому скопіював принцип, але я реалізував його трохи інакше.

Я вважаю, що це дискусійне питання про те, чи повинен це бути метод розширення (оскільки не всі довги мають обов'язково розмір байтів), але мені вони подобаються, і десь я можу знайти метод, коли мені наступний потрібен!

Щодо одиниць, я не думаю, що я ніколи в своєму житті не казав "Кібібайт" або "Мебібайт", і хоча я скептично ставляться до таких застосованих, а не до розвинених стандартів, я вважаю, що це дозволить уникнути плутанини в довгостроковій перспективі .

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

Нижче я використовую метод Long Extension, щоб перетворити на рядки, що читаються людиною. Цей метод є реалізацією рішення C # цього ж питання, розміщеного на C #, розміщеному тут .

/// <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";
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.