Я использую 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;
}
Код, который вы показываете, считывает все содержимое файла размером 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 буферизировать все данные файла перед отправкой первого байта. Используйте это свойство, когда вы знаете, что отправляемый файл очень большой. Данные все равно будут отправлены в браузер правильно.
И даже это не является полным решением. Вам нужно будет установить заголовки ответа и так далее. Думаю, вы знаете об этом.
Вы удваиваете размер буфера при каждом перераспределении, что означает, что ранее выделенные блоки нельзя использовать (они фактически протекают). К тому времени, когда вы дойдете до 500 МБ, вы израсходуете 1 ГБ плюс накладные расходы. Фактически, это может быть 2 ГБ, поскольку, если вы наберете 512 МБ, ваше следующее выделение будет 1 ГБ. В 32-битной системе это приводит к банкротству вашего процесса.
Поскольку вы читаете обычный файл, просто запросите у файловой системы его размер и предварительно выделите буфер за один раз.