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

392

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

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;
}
Tony_Henrich
источник
60
Ваш пример может быть сокращен до byte[] buff = File.ReadAllBytes(fileName).
Джесси С. Slicer
3
Почему веб-служба третьей стороны подразумевает, что файл должен быть полностью в оперативной памяти перед отправкой в ​​веб-службу, а не передаваться в потоковом режиме? Веб-сервис не будет знать разницу.
Брайан
@Brian, Некоторые клиенты не знают, как обрабатывать поток .NET, например, Java. В этом случае все, что можно сделать, это прочитать весь файл в байтовом массиве.
Сжеффри
4
@sjeffrey: я сказал, что данные должны передаваться в потоковом режиме, а не передаваться как поток .NET. Клиенты не будут знать разницу в любом случае.
Брайан

Ответы:

776

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

return File.ReadAllBytes(fileName);

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

Мехрдад Афшари
источник
40
этот метод ограничен 2 ^ 32 байтовыми файлами (4,2 ГБ)
Махмуд Фарахат
11
File.ReadAllBytes генерирует исключение OutOfMemoryException для больших файлов (проверено на 630 МБ файла, и оно не удалось)
sakito
6
@ juanjo.arana Да, ну ... конечно, всегда будет что-то, что не умещается в памяти, и в этом случае нет ответа на вопрос. Как правило, вы должны передавать файл в потоковом режиме, а не хранить его в памяти. Возможно, вы захотите взглянуть на это в качестве временной меры: msdn.microsoft.com/en-us/library/hh285054%28v=vs.110%29.aspx
Мехрдад Афшари,
4
В .NET существует ограничение на размер массива, но в .NET 4.5 вы можете включить поддержку больших массивов (> 2 ГБ), используя специальный параметр конфигурации, см. Msdn.microsoft.com/en-us/library/hh285054.aspx
недопустимо -иммигрант
3
@harag Нет, и вопрос не в этом.
Мехрдад Афшари
72

Я могу утверждать, что ответ здесь, как правило , «не». Если вам абсолютно не нужны все данные одновременно, рассмотрите возможность использования 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);
    }
}
Марк Гравелл
источник
3
Чтобы добавить к вашему утверждению, я даже предлагаю рассмотреть асинхронные обработчики ASP.NET, если у вас есть связанная с вводом / выводом операция, такая как потоковая передача файла клиенту. Однако, если вам по какой-то причине необходимо прочитать весь файл byte[], я советую избегать использования потоков или чего-либо еще и просто использовать предоставляемый системой API.
Мехрдад Афшари
@Mehrdad - согласился; но полный контекст не ясен. Точно так же у MVC есть результаты действий для этого.
Марк Гравелл
Да, мне нужны все данные одновременно. Он собирается на сторонний веб-сервис.
Tony_Henrich
Что такое система предоставляемого API?
Tony_Henrich
1
@ Тони: Я заявил в своем ответе File.ReadAllBytes.
Мехрдад Афшари
33

Я думаю, что это:

byte[] file = System.IO.File.ReadAllBytes(fileName);
Powerlord
источник
3
Обратите внимание, что это может привести к остановке при получении действительно больших файлов.
vapcguy
28

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


источник
Почему тип оборудования имеет значение? Так что, если это IDE, вы используете какой-то метод .NET, а если это RAID, вы используете другой?
Tony_Henrich
@Tony_Henrich - Это не имеет ничего общего с тем, что вы делаете из вашего языка программирования. Существуют разные типы жестких дисков. Например, диски Seagate классифицируются как «AS» или «NS», причем NS - это большой дисковый накопитель на сервере, где в качестве «AS» используется накопитель на базе домашнего компьютера. Скорость поиска и скорость внутренней передачи также влияют на скорость чтения с диска. RAID-массивы могут значительно улучшить производительность чтения / записи за счет кэширования. Таким образом, вы можете прочитать файл сразу, но основное оборудование все еще является решающим фактором.
2
Этот код содержит критическую ошибку. Чтение требуется только для возврата не менее 1 байта.
Мафу
Я бы хотел обернуть приведение типа long к int проверенной конструкцией следующим образом: check ((int) fs.Length)
tzup
Я бы просто сделал var binaryReader = new BinaryReader(fs); fileData = binaryReader.ReadBytes((int)fs.Length);в этом usingзаявлении. Но это по сути похоже на то, что сделал OP, просто я вырезал строку кода, приведя fs.Lengthк нему intвместо того, чтобы получить longзначение FileInfoдлины и преобразовать его.
vapcguy
9

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

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

Joel
источник
Исходный код C # об этом, для управления garbage collector, chunks, производительность, счетчики событий , ...
PreguntonCojoneroCabrón
6

Я бы сказал, что 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замкнуты и утилизированы.

vapcguy
источник
Для C # необходимо использовать «using (FileStream fs = File.OpenRead (fileName))» вместо «using (FileStream fs = new File.OpenRead (fileName))», как указано выше. Только что удалил новое ключевое слово перед File.OpenRead ()
Syed Mohamed
@Syed Код выше был написан для C #, но вы правы, что там newне было необходимости. Удалены.
vapcguy
1

В случае, когда «большой файл» подразумевается за пределами 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;
}
Менно де Рюйтер
источник
0

Используйте класс BufferedStream в C # для повышения производительности. Буфер - это блок байтов в памяти, используемый для кэширования данных, что уменьшает количество обращений к операционной системе. Буферы улучшают производительность чтения и записи.

Ниже приведен пример кода и дополнительные пояснения: http://msdn.microsoft.com/en-us/library/system.io.bufferedstream.aspx

Тодд Моисей
источник
Какой смысл использовать, BufferedStreamкогда вы читаете все это сразу?
Мехрдад Афшари
Он попросил лучшего исполнения, чтобы не читать файл сразу.
Тодд Моисей
9
Производительность измерима в контексте операции. Дополнительная буферизация для потока, который вы последовательно читаете в память, вряд ли выиграет от дополнительного буфера.
Мехрдад Афшари
0

использовать этот:

 bytesRead = responseStream.ReadAsync(buffer, 0, Length).Result;
Диша Шарма
источник
2
Добро пожаловать в переполнение стека! Поскольку пояснения являются важной частью ответов на этой платформе, пожалуйста, объясните свой код и как он решает проблему в вопросе и почему он может быть лучше, чем другие ответы. Наше руководство Как написать хороший ответ может быть полезным для вас. Спасибо
Дэвид
0

Обзор: если ваше изображение добавлено в качестве встроенного ресурса action =, используйте 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

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

Дейв
источник
-7

Если вы имеете дело с файлами размером более 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;
    }
}
elaverick
источник
11
Я не понимаю, как код относится к вопросу (или что вы предлагаете в письменном тексте)
Vojtech B