Найкращий спосіб прочитати великий файл у байтовому масиві на C #?


391

У мене є веб-сервер, який буде читати великі двійкові файли (кілька мегабайт) у байтові масиви. Сервер міг би читати одночасно декілька файлів (різні запити на сторінці), тому я шукаю найбільш оптимізований спосіб зробити це без надто великого оподаткування процесора. Чи достатньо хороший код нижче?

public byte[] FileToByteArray(string fileName)
{
    byte[] buff = null;
    FileStream fs = new FileStream(fileName, 
                                   FileMode.Open, 
                                   FileAccess.Read);
    BinaryReader br = new BinaryReader(fs);
    long numBytes = new FileInfo(fileName).Length;
    buff = br.ReadBytes((int) numBytes);
    return buff;
}

60
Ваш приклад можна скоротити до byte[] buff = File.ReadAllBytes(fileName).
Джессі К. Слікер

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

@Brian, Деякі клієнти не знають, як обробити потік .NET, як, наприклад, Java. У цьому випадку все, що можна зробити, - це прочитати весь файл у байтовому масиві.
sjeffrey

4
@sjeffrey: Я сказав, що дані повинні бути потоковими, а не передаватися як .NET потік. Клієнти не знають різниці в будь-якому випадку.
Брайан

Відповіді:


776

Просто замініть всю справу на:

return File.ReadAllBytes(fileName);

Однак, якщо ви стурбовані витратою пам'яті, вам не слід читати весь файл в пам'ять всі відразу. Ви повинні це робити шматками.


40
цей метод обмежений 2 ^ 32 байтовими файлами (4,2 ГБ)
Махмуд Фарахат

11
File.ReadAllBytes викидає OutOfMemoryException з великими файлами (тестовано з файлом 630 МБ і не вдалося)
sakito

6
@ juanjo.arana Так, ну ... звичайно, завжди знайдеться щось, що не вписується в пам'ять, і в цьому випадку відповіді на питання немає. Як правило, ви повинні передавати файл, а не зберігати його в пам'яті взагалі. Ви можете поглянути на це для запобіжного заходу: msdn.microsoft.com/en-us/library/hh285054%28v=vs.110%29.aspx
Мехрдад

4
Існує обмеження на розмір масиву в .NET, але в .NET 4.5 ви можете ввімкнути підтримку великих масивів (> 2 ГБ) за допомогою спеціального параметра config, див. Msdn.microsoft.com/en-us/library/hh285054.aspx
незаконний -імігрант

3
@harag Ні, і це не запитання.
Мехрдад Афшарі

72

Я можу стверджувати, що відповідь тут взагалі "не". Якщо вам абсолютно не потрібні всі дані одразу, подумайте про використання StreamAPI-інтерфейсу (або якогось варіанта зчитувача / ітератора). Це особливо важливо, коли у вас є кілька паралельних операцій (як це запропоновано запитанням), щоб мінімізувати завантаження системи та максимізувати пропускну здатність.

Наприклад, якщо ви передаєте дані поточному користувачеві:

Stream dest = ...
using(Stream source = File.OpenRead(path)) {
    byte[] buffer = new byte[2048];
    int bytesRead;
    while((bytesRead = source.Read(buffer, 0, buffer.Length)) > 0) {
        dest.Write(buffer, 0, bytesRead);
    }
}

3
Щоб додати до вашої заяви, я навіть пропоную розглянути обробники async ASP.NET, якщо у вас є операція, пов'язана з входом / виводом, як потокове передавання файлу клієнту. Однак, якщо вам доводиться читати весь файл до byte[]якоїсь причини, я пропоную уникати використання потоків або чогось іншого, а просто використовувати наданий системою API.
Мехрдад Афшарі

@Mehrdad - погодився; але повний контекст не зрозумілий. Так само MVC має результати для цього.
Marc Gravell

Так, мені потрібні всі дані одразу. Він переходить до сторонньої веб-служби.
Tony_Henrich

Що таке API, що надається системою?
Tony_Henrich

1
@Tony: Я сказав у своїй відповіді: File.ReadAllBytes.
Мехрдад Афшарі

32

Я б подумав так:

byte[] file = System.IO.File.ReadAllBytes(fileName);

3
Зауважте, що це може затриматись при отриманні дійсно великих файлів.
vapcguy

28

Ваш код може бути врахований до цього (замість File.ReadAllBytes):

public byte[] ReadAllBytes(string fileName)
{
    byte[] buffer = null;
    using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read))
    {
        buffer = new byte[fs.Length];
        fs.Read(buffer, 0, (int)fs.Length);
    }
    return buffer;
} 

Зверніть увагу на Integer.MaxValue - обмеження розміру файлу, розміщене методом Read. Іншими словами, ви можете прочитати лише шматок 2 Гб одночасно.

Також зауважте, що останнім аргументом FileStream є розмір буфера.

Я б також запропонував прочитати про FileStream та BufferedStream .

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

Також ваш базовий апарат матиме великий вплив на продуктивність. Ви використовуєте серверні диски на жорсткому диску з великими кешами та RAID-карту з вбудованим кешем пам'яті? Або ви використовуєте стандартний привід, підключений до порту IDE?


Чому тип апаратних засобів має значення? Отже, якщо це IDE, ви використовуєте якийсь метод .NET, а якщо це RAID, ви використовуєте інший?
Tony_Henrich

@Tony_Henrich - Це не має нічого спільного з тими дзвінками, які ви робите зі своєї мови програмування. Існують різні типи накопичувачів жорсткого диска. Наприклад, накопичувачі Seagate класифікуються як "AS" або "NS", причому NS є сервером, великим кеш-накопичувачем, де - як "AS" накопичувач - споживач - домашній комп'ютер. Швидкість пошуку та швидкість внутрішньої передачі також впливають на те, як швидко ви можете прочитати щось з диска. RAID-масиви можуть значно покращити продуктивність читання / запису за допомогою кешування. Тож ви зможете прочитати файл одразу, але вирішальний фактор все ще є базовим обладнанням.

2
Цей код містить критичну помилку. Для читання потрібно лише повернути щонайменше 1 байт.
мафу

Я б переконався, щоб обернути довгий інт-каст із перевіреною конструкцією так: перевірено ((int) fs.Length)
tzup

Я б просто зробити var binaryReader = new BinaryReader(fs); fileData = binaryReader.ReadBytes((int)fs.Length);в цьому usingзаяві. Але це ефективно , як то , що ОП зробив, просто я вирізати рядок коди лиття , fs.Lengthщоб intзамість того , щоб отримувати longзначення FileInfoдовжини і перетворення цього.
vapcguy

9

Залежно від частоти операцій, розміру файлів та кількості файлів, які ви переглядаєте, є інші проблеми з ефективністю, які слід враховувати. Варто пам’ятати, що кожен ваш байтовий масив буде випущений на волю сміттєзбірника. Якщо ви не кешуєте жодними з цих даних, ви можете створити багато сміття і втратити більшу частину своєї продуктивності, щоб % Час в GC. Якщо шматки перевищують 85 К, ви виділите на велику кучу об’єктів (LOH), яка потребує звільнення колекцій усіх поколінь (це дуже дорого, і на сервері буде припинено все виконання, поки воно триває ). Крім того, якщо у вас є багато об’єктів на LOH, ви можете закінчити фрагментацію LOH (LOH ніколи не ущільнюється), що призводить до низької продуктивності та винятків з пам'яті. Ви можете переробити процес, як тільки потрапите в певний момент, але я не знаю, чи це найкраща практика.

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


Вихідний код C # про це, для управління garbage collector, chunks, продуктивність, лічильники подій , ...
PreguntonCojoneroCabrón

6

Я б сказав, BinaryReaderце добре, але його можна відновити замість усіх цих рядків коду для отримання довжини буфера:

public byte[] FileToByteArray(string fileName)
{
    byte[] fileData = null;

    using (FileStream fs = File.OpenRead(fileName)) 
    { 
        using (BinaryReader binaryReader = new BinaryReader(fs))
        {
            fileData = binaryReader.ReadBytes((int)fs.Length); 
        }
    }
    return fileData;
}

Має бути краще, ніж використовувати .ReadAllBytes(), оскільки я побачив у коментарях до верхньої відповіді, що включає, .ReadAllBytes()що один із коментаторів мав проблеми з файлами> 600 Мб, оскільки a BinaryReaderпризначений для подібних речей. Крім того, внесення його у usingвиписку забезпечує FileStreamі BinaryReaderзакриття, і розпорядження.


Для C # потрібно використовувати "using (FileStream fs = File.OpenRead (fileName))" замість "використання (FileStream fs = new File.OpenRead (fileName))", як зазначено вище. Щойно видалили нове ключове слово перед File.OpenRead ()
Syed Mohamed

@Syed Код, що був вище, написаний для C #, але ти маєш рацію, що newтам не потрібен. Вилучено.
vapcguy

1

У випадку, якщо "великий файл" мається на увазі понад 4 Гб, тоді моя наступна письмова логіка коду підходить. Основна проблема, яку слід помітити - це тип даних LONG, який використовується методом SEEK. Оскільки LONG здатний вказувати за межі 2 ^ 32 меж даних. У цьому прикладі код обробляє спочатку обробку великого файлу шматками в 1 Гб, після обробки великих цілих шматів 1 ГБ обробляються залишені (<1 ГБ) байти. Я використовую цей код для обчислення CRC файлів, що перевищують розмір 4 ГБ. (використовуючи https://crc32c.machinezoo.com/ для розрахунку crc32c у цьому прикладі)

private uint Crc32CAlgorithmBigCrc(string fileName)
{
    uint hash = 0;
    byte[] buffer = null;
    FileInfo fileInfo = new FileInfo(fileName);
    long fileLength = fileInfo.Length;
    int blockSize = 1024000000;
    decimal div = fileLength / blockSize;
    int blocks = (int)Math.Floor(div);
    int restBytes = (int)(fileLength - (blocks * blockSize));
    long offsetFile = 0;
    uint interHash = 0;
    Crc32CAlgorithm Crc32CAlgorithm = new Crc32CAlgorithm();
    bool firstBlock = true;
    using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read))
    {
        buffer = new byte[blockSize];
        using (BinaryReader br = new BinaryReader(fs))
        {
            while (blocks > 0)
            {
                blocks -= 1;
                fs.Seek(offsetFile, SeekOrigin.Begin);
                buffer = br.ReadBytes(blockSize);
                if (firstBlock)
                {
                    firstBlock = false;
                    interHash = Crc32CAlgorithm.Compute(buffer);
                    hash = interHash;
                }
                else
                {
                    hash = Crc32CAlgorithm.Append(interHash, buffer);
                }
                offsetFile += blockSize;
            }
            if (restBytes > 0)
            {
                Array.Resize(ref buffer, restBytes);
                fs.Seek(offsetFile, SeekOrigin.Begin);
                buffer = br.ReadBytes(restBytes);
                hash = Crc32CAlgorithm.Append(interHash, buffer);
            }
            buffer = null;
        }
    }
    //MessageBox.Show(hash.ToString());
    //MessageBox.Show(hash.ToString("X"));
    return hash;
}

0

Використовуйте клас BufferedStream в C # для підвищення продуктивності. Буфер - це блок байтів у пам'яті, який використовується для кешування даних, тим самим зменшуючи кількість викликів до операційної системи. Буфери покращують ефективність читання та запису.

Дивіться наступний приклад коду та додаткове пояснення: http://msdn.microsoft.com/en-us/library/system.io.bufferedstream.aspx


Який сенс використання а, BufferedStreamколи ви читаєте всю справу одразу?
Мехрдад Афшарі

Він попросив найкращого вигляду не прочитати файл одразу.
Тодд Мойсей

9
Продуктивність вимірюється в контексті операції. Додаткове буферизація для потоку, який ви читаєте послідовно, все відразу, для пам'яті, швидше за все, не скористається додатковим буфером.
Мехрдад Афшарі

0

використовуй це:

 bytesRead = responseStream.ReadAsync(buffer, 0, Length).Result;

2
Ласкаво просимо до переповнення стека! Оскільки пояснення є важливою частиною відповідей на цій платформі, будь ласка, поясніть свій код та як він вирішує проблему у питанні та чому він може бути кращим, ніж інші відповіді. Наш посібник Як написати гарну відповідь може бути корисним для вас. Спасибі
Девід

-4

Я рекомендую спробувати Response.TransferFile()метод, а потім Response.Flush()і Response.End()для обслуговування великих файлів.


-7

Якщо ви маєте справу з файлами розміром понад 2 Гб, ви побачите, що вищевказані методи не працюють.

Набагато простіше просто передати потік в MD5 і дозволити, щоб фрагмент вашого файлу був для вас:

private byte[] computeFileHash(string filename)
{
    MD5 md5 = MD5.Create();
    using (FileStream fs = new FileStream(filename, FileMode.Open))
    {
        byte[] hash = md5.ComputeHash(fs);
        return hash;
    }
}

11
Я не бачу, наскільки код стосується питання (або того, що ви пропонуєте в письмовому тексті)
Vojtech B
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.