Лучший способ прочитать большой файл в байтовый массив в C#?
У меня есть веб-сервер, который будет читать большие двоичные файлы (несколько мегабайт) в байтовые массивы. Сервер может считывать несколько файлов одновременно (разные запросы страниц), поэтому я ищу наиболее оптимизированный способ сделать это без чрезмерной нагрузки на процессор. Код ниже достаточно хорош?
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;
}
9 ответов
Просто замените все это:
return File.ReadAllBytes(fileName);
Однако, если вас беспокоит потребление памяти, вам не следует читать весь файл в память сразу. Вы должны сделать это кусками.
Я могу утверждать, что ответ здесь, как правило, "не". Если вам не нужны все данные сразу, рассмотрите возможность использования Stream
API (или некоторый вариант читателя / итератора). Это особенно важно, когда у вас есть несколько параллельных операций (как предполагает вопрос), чтобы минимизировать нагрузку на систему и максимизировать пропускную способность.
Например, если вы передаете данные абоненту:
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);
}
}
Я думаю, что это:
byte[] file = System.IO.File.ReadAllBytes(fileName);
Ваш код может быть учтен к этому (вместо 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?
Я бы сказал 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 МБ, так как BinaryReader
предназначен для такого рода вещей. Кроме того, положить его в using
заявление обеспечивает FileStream
а также BinaryReader
закрыты и расположены.
В зависимости от частоты операций, размера файлов и количества просматриваемых файлов, существуют другие проблемы с производительностью, которые необходимо учитывать. Следует помнить одну вещь: каждый из ваших байтовых массивов будет освобожден во власти сборщика мусора. Если вы не кешируете какие-либо из этих данных, вы можете создать много мусора и потерять большую часть своей производительности до % Time в GC. Если чанки больше 85 Кб, вы будете выделять кучу больших объектов (LOH), для освобождения которой потребуется коллекция всех поколений (это очень дорого, и на сервере остановит все выполнение во время работы).). Кроме того, если у вас есть тонна объектов в LOH, вы можете получить фрагментацию LOH (LOH никогда не уплотняется), что приводит к низкой производительности и исключениям нехватки памяти. Вы можете перезапустить процесс, как только достигнете определенной точки, но я не знаю, является ли это лучшей практикой.
Суть в том, что вы должны учитывать полный жизненный цикл своего приложения, прежде чем просто просто считать все байты в памяти самым быстрым способом, или же вы можете обменять краткосрочную производительность на общую производительность.
Обзор: если ваше изображение добавлено в качестве ресурса action= embedded, используйте GetExecutingAssembly для извлечения ресурса jpg в поток, а затем прочитайте двоичные данные в потоке в массив байтов.
public byte[] GetAImage()
{
byte[] bytes=null;
var assembly = Assembly.GetExecutingAssembly();
var resourceName = "MYWebApi.Images.X_my_image.jpg";
using (Stream stream = assembly.GetManifestResourceStream(resourceName))
{
bytes = new byte[stream.Length];
stream.Read(bytes, 0, (int)stream.Length);
}
return bytes;
}
В случае, когда "большой файл" подразумевается за пределами 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;
}
Используйте класс BufferedStream в C# для повышения производительности. Буфер - это блок байтов в памяти, используемый для кеширования данных, что уменьшает количество обращений к операционной системе. Буферы улучшают производительность чтения и записи.
Ниже приведен пример кода и дополнительные пояснения: http://msdn.microsoft.com/en-us/library/system.io.bufferedstream.aspx
Использовать этот:
bytesRead = responseStream.ReadAsync(buffer, 0, Length).Result;
Я бы порекомендовал попробовать Response.TransferFile()
метод, то Response.Flush()
а также Response.End()
для обслуживания ваших больших файлов.
Если вы имеете дело с файлами размером более 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;
}
}