OutOfMemoryException, когда я читаю 500 МБ FileStream - PullRequest
9 голосов
/ 11 мая 2010

Я использую 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;
    }

Ответы [ 3 ]

33 голосов
/ 11 мая 2010

Код, который вы показываете, считывает все содержимое файла 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 ГБ. В какой-то момент вы достигнете предела памяти в вашей системе и у вас будет та же ошибка нехватки памяти, с которой вы начали.

Проблема в том, что вы пытаетесь одновременно удерживать все содержимое файла в памяти. Обычно это не нужно и обречено на провал, поскольку файлы увеличиваются в размере. Это не проблема, когда размер файла составляет 16 КБ. На 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);
   }
} 

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

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

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

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

4 голосов
/ 11 мая 2010

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

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

0 голосов
/ 11 октября 2018

Промежуточное программное обеспечение ядра Asp.Net

public static async Task<string> GetRequestBody(HttpContext context)
    {
        string bodyText = string.Empty;
        try
        {
            var requestbody = context.Request.Body;
            context.Request.EnableRewind();
            int offset = 0, bytesread = 0;
            var buffer = new byte[5096];
            while ((bytesread = await context.Request.Body.ReadAsync(buffer, offset, buffer.Length - offset)) > 0)
            {
                offset += bytesread;
                if (offset == buffer.Length)
                {
                    int nextByte = context.Request.Body.ReadByte();
                    if (nextByte == -1)
                    {
                        break;
                    }
                    byte[] newBuffer = new byte[buffer.Length * 2];
                    Array.Copy(buffer, newBuffer, buffer.Length);//how to avoid copy 
                    newBuffer[offset] = (byte)nextByte;//how to avoid boxing 
                    buffer = newBuffer;
                    offset++;
                }
                if (offset > 4194304)
                {
                    //log.Warn("Middleware/GetRequestBody--> Request length exceeding limit");
                    break;
                }
            }
            bodyText = Encoding.UTF8.GetString(buffer);
        }
        catch (Exception ex)
        {
            //log.Debug(ex, "Middleware/GetRequestBody--> Request length exceeding limit");
        }
        context.Request.Body.Position = 0;
        return bodyText;
    }
...