Лучший способ прочитать большой файл в байтовый массив в C #? - PullRequest
358 голосов
/ 09 января 2010

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

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;
}

Ответы [ 11 ]

733 голосов
/ 09 января 2010

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

return File.ReadAllBytes(fileName);

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

62 голосов
/ 09 января 2010

Я мог бы утверждать, что ответ здесь обычно - "не". Если вам абсолютно не нужно все данные сразу, рассмотрите возможность использования API на основе Stream (или некоторого варианта считывателя / итератора). Это особенно важно, когда у вас есть несколько параллельных операций (как предполагает вопрос), чтобы минимизировать нагрузку на систему и максимизировать пропускную способность.

Например, если вы передаете данные вызывающей стороне:

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);
    }
}
30 голосов
/ 09 января 2010

Я бы подумал:

byte[] file = System.IO.File.ReadAllBytes(fileName);
24 голосов
/ 09 января 2010

Ваш код может быть учтен к этому (вместо 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?

9 голосов
/ 09 января 2010

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

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

6 голосов
/ 12 октября 2016

Я бы сказал, 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.

1 голос
/ 26 апреля 2019

В случае, если «большой файл» подразумевается за пределами 4 ГБ, тогда применима моя следующая логика написанного кода. Ключевой вопрос, на который следует обратить внимание, - это тип данных LONG, используемый с методом SEEK. Поскольку LONG способен указывать за пределы 2 ^ 32 границ данных. В этом примере код обрабатывает сначала обработку большого файла кусками по 1 ГБ, после обработки больших целых кусков по 1 ГБ обрабатываются оставшиеся (<1 ГБ) байты. Я использую этот код для расчета CRC файлов, размер которых превышает 4 ГБ. (используя <a href="https://crc32c.machinezoo.com/" rel="nofollow noreferrer">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;
}
0 голосов
/ 13 апреля 2019

используйте это:

 bytesRead = responseStream.ReadAsync(buffer, 0, Length).Result;
0 голосов
/ 20 октября 2014

Если вы имеете дело с файлами размером более 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;
    }
}
0 голосов
/ 20 января 2010

Я бы рекомендовал попробовать метод Response.TransferFile(), а затем Response.Flush() и Response.End() для обслуживания ваших больших файлов.

...