Лучший способ прочитать большой файл в массив байтов на С#?

StackOverflow https://stackoverflow.com/questions/2030847

Вопрос

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

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;
}
Это было полезно?

Решение

Просто замените все это на:

return File.ReadAllBytes(fileName);

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

Другие советы

Я могу утверждать, что ответ здесь в целом это «не делай».Если только ты не абсолютно необходимо все данные одновременно, рассмотрите возможность использования 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);
    }
}

Я бы подумал так:

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 — это размер буфера.

Я бы также посоветовал прочитать о Файловый поток и Буферизованный поток.

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

Также ваше базовое оборудование будет иметь большое влияние на производительность.Используете ли вы серверные жесткие диски с большим кэшем и карту RAID со встроенным кэшем памяти?Или вы используете стандартный диск, подключенный к порту IDE?

В зависимости от частоты операций, размера файлов и количества просматриваемых файлов существуют и другие проблемы с производительностью, которые следует учитывать.Следует помнить одну вещь: каждый из ваших байтовых массивов будет освобожден на милость сборщика мусора.Если вы не кэшируете какие-либо данные, вы можете создать много мусора и потерять большую часть своей производительности. % времени в GC.Если фрагменты превышают 85 КБ, вы будете выделять их в кучу больших объектов (LOH), для освобождения которой потребуется коллекция всех поколений (это очень дорого, и на сервере все выполнение будет остановлено, пока оно происходит). ).Кроме того, если у вас есть масса объектов в LOH, вы можете столкнуться с фрагментацией LOH (LOH никогда не сжимается), что приводит к снижению производительности и исключениям нехватки памяти.Вы можете перезапустить процесс, как только достигнете определенной точки, но я не знаю, является ли это лучшей практикой.

Дело в том, что вам следует рассмотреть полный жизненный цикл вашего приложения, прежде чем обязательно просто считывать все байты в память как можно быстрее, иначе вы можете пожертвовать краткосрочной производительностью ради общей производительности.

я бы сказал 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 закрываются и утилизируются.

В случае, если под «большим файлом» подразумевается размер, превышающий предел в 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;
    }
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top