OutOfMemoryException, когда я считал 500 МБ FileStream

Я использую Filestream для чтения большой файл (> 500 МБ), и я получаю OutOfMemoryException.

Любые решения об этом.

Мой Код:

 using (var fs3 = new FileStream(filePath2, FileMode.Open, FileAccess.Read))
                {
                    byte[] b2 = ReadFully(fs3, 1024);
                }


 public static byte[] ReadFully(Stream stream, int initialLength)
    {
        // If we've been passed an unhelpful initial length, just
        // use 32K.
        if (initialLength < 1)
        {
            initialLength = 32768;
        }

        byte[] buffer = new byte[initialLength];
        int read = 0;

        int chunk;
        while ((chunk = stream.Read(buffer, read, buffer.Length - read)) > 0)
        {
            read += chunk;

            // If we've reached the end of our buffer, check to see if there's
            // any more information
            if (read == buffer.Length)
            {
                int nextByte = stream.ReadByte();

                // End of stream? If so, we're done
                if (nextByte == -1)
                {
                    return buffer;
                }

                // Nope. Resize the buffer, put in the byte we've just
                // read, and continue
                byte[] newBuffer = new byte[buffer.Length * 2];
                Array.Copy(buffer, newBuffer, buffer.Length);
                newBuffer[read] = (byte)nextByte;
                buffer = newBuffer;
                read++;
            }
        }
        // Buffer is now too big. Shrink it.
        byte[] ret = new byte[read];
        Array.Copy(buffer, ret, read);
        return ret;
    }
9
задан Charles 17 March 2015 в 16:48
поделиться

2 ответа

Код, который вы показываете, считывает все содержимое файла размером 500 мб в непрерывную область памяти. Неудивительно, что вы получаете состояние "вне памяти".

Решение - "не делайте этого".

Что вы на самом деле пытаетесь сделать?


Если вы хотите прочитать файл полностью, это гораздо проще, чем используемый вами метод ReadFully. Попробуйте это:

using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read)) 
{ 
   byte[] buffer = new byte[fs.Length];
   int bytesRead = fs.Read(buffer, 0, buffer.Length);
   // buffer now contains the entire contents of the file
} 

Но... использование этого кода не решит вашу проблему. Он может сработать для файла размером 500 мб. Он не сработает для файла размером 750мб или 1гб. В какой-то момент вы достигнете предела памяти в вашей системе и получите ту же ошибку, с которой начали.

Проблема в том, что вы пытаетесь удержать в памяти все содержимое файла за один раз. Обычно в этом нет необходимости, и это обречено на неудачу по мере роста размера файлов. Это не проблема, когда размер файла составляет 16k. При размере 500 мб это неправильный подход.

Вот почему я несколько раз спрашивал, что вы на самом деле пытаетесь сделать?


Похоже, вы хотите отправить содержимое файла в ответный поток ASPNET. Это и есть вопрос. Не "как прочитать файл размером 500мб в память?". Но "как отправить большой файл в поток ответа ASPNET?"

Для этого, повторюсь, все довольно просто.

// emit the contents of a file into the ASPNET Response stream
using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read)) 
{ 
   Response.BufferOutput= false;   // to prevent buffering
   byte[] buffer = new byte[1024];
   int bytesRead = 0;
   while ((bytesRead = fs.Read(buffer, 0, buffer.Length)) > 0) 
   {
       Response.OutputStream.Write(buffer, 0, bytesRead);
   }
} 

Что он делает, так это итеративно читает фрагмент из файла и записывает этот фрагмент в поток Response, пока в файле больше нечего читать. Это то, что подразумевается под "потоковым вводом-выводом". Данные проходят через вашу логику, но никогда не задерживаются в одном месте, подобно тому, как поток воды проходит через шлюз. В этом примере в памяти никогда не находится более 1k файловых данных одновременно (ну, во всяком случае, не удерживаемых вашим кодом приложения. Есть другие буферы ввода-вывода, расположенные ниже в стеке.)

Это общий паттерн в потоковом вводе-выводе. Изучите его, используйте его.

Единственная хитрость при выкачивании данных в ASPNET's Response.OutputStream - установить BufferOutput = false. По умолчанию ASPNET пытается буферизировать свой вывод. В данном случае (файл размером 500мб) буферизация - плохая идея. Установка свойства BufferOutput в false предотвратит попытку ASPNET буферизировать все данные файла перед отправкой первого байта. Используйте это свойство, когда вы знаете, что отправляемый файл очень большой. Данные все равно будут отправлены в браузер правильно.

И даже это не является полным решением. Вам нужно будет установить заголовки ответа и так далее. Думаю, вы знаете об этом.

33
ответ дан 4 December 2019 в 06:29
поделиться

Вы удваиваете размер буфера при каждом перераспределении, что означает, что ранее выделенные блоки нельзя использовать (они фактически протекают). К тому времени, когда вы дойдете до 500 МБ, вы израсходуете 1 ГБ плюс накладные расходы. Фактически, это может быть 2 ГБ, поскольку, если вы наберете 512 МБ, ваше следующее выделение будет 1 ГБ. В 32-битной системе это приводит к банкротству вашего процесса.

Поскольку вы читаете обычный файл, просто запросите у файловой системы его размер и предварительно выделите буфер за один раз.

4
ответ дан 4 December 2019 в 06:29
поделиться
Другие вопросы по тегам:

Похожие вопросы: