Подтвердить что ты не робот

Чтение больших текстовых файлов с потоками в С#

У меня есть прекрасная задача разработать, как обрабатывать большие файлы, загружаемые в наш редактор приложений script (это похоже на VBA для нашего внутреннего продукта для быстрых макросов). Большинство файлов составляют около 300-400 килобайт, что является хорошей загрузкой. Но когда они выходят за рамки 100 Мб, процесс имеет трудное время (как и следовало ожидать).

Что происходит, так это то, что файл читается и перемещается в RichTextBox, который затем перемещается - не беспокойтесь слишком много об этой части.

Разработчик, который написал исходный код, просто использует StreamReader и делает

[Reader].ReadToEnd()

который может занять довольно много времени.

Моя задача - разбить этот бит кода, прочитать его в кусках в буфер и показать панель прогресса с возможностью ее отмены.

Некоторые предположения:

  • Большинство файлов будут 30-40 MB
  • Содержимое файла является текстовым (не двоичным), некоторые являются Unix-форматом, некоторые DOS.
  • После получения содержимого мы выясним, какой терминатор используется.
  • Никто не беспокоился, когда он загрузил время, необходимое для рендеринга в richtextbox. Это просто начальная загрузка текста.

Теперь для вопросов:

  • Могу ли я просто использовать StreamReader, а затем проверить свойство Length (так что ProgressMax) и выдать значение Read для заданного размера буфера и выполнить итерацию через цикл WHILST внутри рабочего фона, поэтому он не блокирует основной поток пользовательского интерфейса? Затем верните stringbuilder в основной поток после его завершения.
  • Содержимое будет передано в StringBuilder. могу ли я инициализировать StringBuilder с размером потока, если длина доступна?

Являются ли эти (в ваших профессиональных мнениях) хорошие идеи? В прошлом у меня было несколько проблем с чтением контента из Streams, потому что он всегда будет пропускать последние несколько байтов или что-то в этом роде, но я задам еще один вопрос, если это так.

4b9b3361

Ответ 1

Вы можете улучшить скорость чтения с помощью BufferedStream, например:

using (FileStream fs = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
using (BufferedStream bs = new BufferedStream(fs))
using (StreamReader sr = new StreamReader(bs))
{
    string line;
    while ((line = sr.ReadLine()) != null)
    {

    }
}

Март 2013 UPDATE

Недавно я написал код для чтения и обработки (поиск текста) 1 текстовых файлов GB-ish (намного больше, чем файлы, используемые здесь) и достиг значительного прироста производительности с использованием шаблона производителя/потребителя. Задача производителя читается в строках текста с помощью BufferedStream и передается в отдельную потребительскую задачу, которая выполняла поиск.

Я использовал это как возможность изучить TPL Dataflow, который очень хорошо подходит для быстрого кодирования этого шаблона.

Почему BufferedStream работает быстрее

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

Декабрь 2014 года ОБНОВЛЕНИЕ: ваш пробег может меняться

Основываясь на комментариях, FileStream должен использовать BufferedStream внутренне. В то время, когда этот ответ был впервые предоставлен, я измерил значительное повышение производительности, добавив BufferedStream. В то время я ориентировался на .NET 3.x на 32-битной платформе. Сегодня, ориентируясь на .NET 4.5 на 64-битной платформе, я не вижу никаких улучшений.

Похожие

Я столкнулся с ситуацией, когда потоки большого, сгенерированного CSV файла в поток Response из действия ASP.Net MVC были очень медленными. Добавление BufferedStream в этом случае улучшило производительность на 100x. Подробнее см. Небуферизованный вывод очень медленно

Ответ 2

Вы говорите, что вас попросили показать индикатор выполнения при загрузке большого файла. Это потому, что пользователи действительно хотят видеть точный процент загрузки файлов или просто потому, что им нужна визуальная обратная связь, что-то происходит?

Если последнее верно, то решение становится намного проще. Просто сделайте reader.ReadToEnd() в фоновом потоке и отобразите индикатор выполнения шага, а не правильный.

Я поднимаю этот момент, потому что, по моему опыту, это часто бывает. Когда вы пишете программу обработки данных, пользователи, безусловно, будут заинтересованы в% полной фигуре, но для простых, но медленных обновлений пользовательского интерфейса они, скорее всего, просто хотят знать, что компьютер не разбился.: -)

Ответ 3

Если вы читаете производительность и контрольные показатели на этом веб-сайте, вы увидите, что самый быстрый способ чтения (потому что чтение, и обработка все различна) текстовым файлом является следующий фрагмент кода:

using (StreamReader sr = File.OpenText(fileName))
{
    string s = String.Empty;
    while ((s = sr.ReadLine()) != null)
    {
        //do your stuff here
    }
}

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

Ответ 4

Для двоичных файлов самый быстрый способ их чтения я нашел это.

 MemoryMappedFile mmf = MemoryMappedFile.CreateFromFile(file);
 MemoryMappedViewStream mms = mmf.CreateViewStream();
 using (BinaryReader b = new BinaryReader(mms))
 {
 }

В моих тестах он в сотни раз быстрее.

Ответ 5

Используйте фонового рабочего и читайте только ограниченное количество строк. Подробнее читайте только при прокрутке пользователя.

И попробуйте никогда не использовать ReadToEnd(). Это одна из функций, которые вы думаете "зачем они это сделали?"; это script kiddies помощник, который отлично подходит для мелочей, но, как вы видите, это отстой для больших файлов...

Те, кто говорит вам использовать StringBuilder, должны чаще читать MSDN:

Особенности производительности
Методы Concat и AppendFormat объединяют новые данные с существующим объектом String или StringBuilder. Операция конкатенации объекта String всегда создает новый объект из существующей строки и новых данных. Объект StringBuilder поддерживает буфер для размещения конкатенации новых данных. Новые данные добавляются в конец буфера, если комната доступна; в противном случае выделяется новый, более крупный буфер, данные из исходного буфера копируются в новый буфер, а затем новые данные добавляются в новый буфер. Производительность операции конкатенации для объекта String или StringBuilder зависит от того, как часто происходит распределение памяти.

Операция конкатенации строк всегда выделяет память, тогда как операция конкатенации StringBuilder выделяет только память, если буфер объекта StringBuilder слишком мал для размещения новых данных. Следовательно, класс String является предпочтительным для операции конкатенации, если фиксированное число объектов String конкатенировано. В этом случае отдельные операции конкатенации могут быть даже объединены в одну операцию компилятором. Объект StringBuilder предпочтительнее для операции конкатенации, если произвольное количество строк конкатенировано; например, если цикл объединяет случайное число строк пользовательского ввода.

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

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

Ответ 6

Этого должно быть достаточно, чтобы вы начали.

class Program
{        
    static void Main(String[] args)
    {
        const int bufferSize = 1024;

        var sb = new StringBuilder();
        var buffer = new Char[bufferSize];
        var length = 0L;
        var totalRead = 0L;
        var count = bufferSize; 

        using (var sr = new StreamReader(@"C:\Temp\file.txt"))
        {
            length = sr.BaseStream.Length;               
            while (count > 0)
            {                    
                count = sr.Read(buffer, 0, bufferSize);
                sb.Append(buffer, 0, count);
                totalRead += count;
            }                
        }

        Console.ReadKey();
    }
}

Ответ 7

Посмотрите следующий фрагмент кода. Вы упомянули Most files will be 30-40 MB. Это заявляет, что читал 180 Мб за 1,4 секунды на Intel Quad Core:

private int _bufferSize = 16384;

private void ReadFile(string filename)
{
    StringBuilder stringBuilder = new StringBuilder();
    FileStream fileStream = new FileStream(filename, FileMode.Open, FileAccess.Read);

    using (StreamReader streamReader = new StreamReader(fileStream))
    {
        char[] fileContents = new char[_bufferSize];
        int charsRead = streamReader.Read(fileContents, 0, _bufferSize);

        // Can't do much with 0 bytes
        if (charsRead == 0)
            throw new Exception("File is 0 bytes");

        while (charsRead > 0)
        {
            stringBuilder.Append(fileContents);
            charsRead = streamReader.Read(fileContents, 0, _bufferSize);
        }
    }
}

Оригинальная статья

Ответ 8

Вам может быть лучше использовать файлы с отображением памяти здесь. Поддержка файлов с отображением памяти будет в .NET 4 (Я думаю... я слышал, что кто-то еще об этом говорит), следовательно, эта оболочка, которая использует p/invokes, выполняет ту же работу.

Изменить: Смотрите здесь MSDN, как это работает, здесь blog, в котором указано, как это делается в предстоящем .NET 4, когда он выходит как выпуск. Ссылка, которую я дал ранее, представляет собой обертку вокруг pinvoke для достижения этой цели. Вы можете отобразить весь файл в память и просмотреть его как скользящее окно при прокрутке файла.

Ответ 9

Итератор может быть идеальным для этого типа работы:

public static IEnumerable<int> LoadFileWithProgress(string filename, StringBuilder stringData)
{
    const int charBufferSize = 4096;
    using (FileStream fs = File.OpenRead(filename))
    {
        using (BinaryReader br = new BinaryReader(fs))
        {
            long length = fs.Length;
            int numberOfChunks = Convert.ToInt32((length / charBufferSize)) + 1;
            double iter = 100 / Convert.ToDouble(numberOfChunks);
            double currentIter = 0;
            yield return Convert.ToInt32(currentIter);
            while (true)
            {
                char[] buffer = br.ReadChars(charBufferSize);
                if (buffer.Length == 0) break;
                stringData.Append(buffer);
                currentIter += iter;
                yield return Convert.ToInt32(currentIter);
            }
        }
    }
}

Вы можете вызвать его, используя следующее:

string filename = "C:\\myfile.txt";
StringBuilder sb = new StringBuilder();
foreach (int progress in LoadFileWithProgress(filename, sb))
{
    // Update your progress counter here!
}
string fileData = sb.ToString();

Когда файл будет загружен, итератор вернет номер выполнения от 0 до 100, который вы можете использовать для обновления вашей панели выполнения. Как только цикл закончен, StringBuilder будет содержать содержимое текстового файла.

Кроме того, поскольку вы хотите текст, мы можем просто использовать BinaryReader для чтения в символах, что обеспечит правильное выравнивание ваших буферов при чтении любых многобайтовых символов (UTF-8, UTF-16 и т.д.).

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

Ответ 10

Я знаю, что эти вопросы довольно старые, но я нашел его на днях и протестировал рекомендацию для MemoryMappedFile, и это самый быстрый метод. Сравнение - это чтение файла с разрешением 7,616,939 строк по 345 МБ с помощью метода readline, который занимает 12 часов на моей машине при выполнении одной и той же нагрузки, и чтение через MemoryMappedFile занимает 3 секунды.

Я хотел опубликовать это в комментариях к этому предложению, но мой "представитель" недостаточно высок, чтобы сделать это. Я хотел обратить внимание на это, потому что я искал в Интернете и тестировал каждую рекомендацию, которую я мог найти, чтобы вернуться и проверить MemoryMapedFile успешно.