2010-01-29 2 views
70

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

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

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

[Reader].ReadToEnd() 

, который может занять некоторое время, чтобы закончить.

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

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

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

Теперь вопросы:

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

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

+26

30-40MB файлы сценариев? Святая скумбрия! Мне бы очень хотелось, чтобы код был проверен ... – dthorpe

+0

Это всего лишь несколько строк кода. См. Эту библиотеку, которую я использую для чтения 25 ГБ и более больших файлов. https://github.com/Agenty/FileReader/ – Vicky

ответ

6

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

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

Эти ребята говорят вам использовать StringBuilder нужно читать MSDN чаще:

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

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

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

+0

далеко вы, ребята, супер быстрые! к сожалению, из-за того, как работает макрос, весь поток необходимо загрузить. Как я уже говорил, не беспокойтесь о богатой части. Его первоначальная загрузка мы хотим улучшить. –

+0

, чтобы вы могли работать по частям, читать первые строки X, применять макрос, читать строки X, применять макрос и т. Д. , если вы объясните, что делает этот макрос, мы можем помочь вам с большей точностью – Tufo

2

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

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

4

Посмотрите следующий фрагмент кода. Вы упомянули 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); 
     } 
    } 
} 

Original Article

+3

Эти тесты, как известно, ненадежны. Когда вы повторите тест, вы будете читать данные из кеша файловой системы. Это, по крайней мере, на порядок быстрее, чем настоящий тест, который считывает данные с диска. Файл размером 180 МБ не может занимать менее 3 секунд. Перезагрузите компьютер, запустите тест один раз для реального номера. –

+6

строка stringBuilder.Append потенциально опасна, вам нужно заменить ее на stringBuilder.Append (fileContents, 0, charsRead); чтобы вы не добавляли полные 1024 символа, даже когда поток закончился раньше. –

5

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

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(); 
    } 
} 
+3

Я бы переместил «var buffer = new char [1024]» из цикла: нет необходимости создавать новый буфер каждый раз. Просто поставьте его перед «while (count> 0)». –

14

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

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

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

+2

Но может ли пользователь отказаться от вызова ReadToEnd? –

+0

@Tim, хорошо пятнистый. В этом случае мы вернемся в цикл 'StreamReader'. Тем не менее, это все равно будет проще, потому что нет необходимости читать дальше, чтобы рассчитать индикатор прогресса. –

1

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

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 и т. Д.).

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

142

Вы можете улучшить скорость чтения с помощью 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 ОБНОВЛЕНИЕ

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

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

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

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

декабря 2014 UPDATE: Ваша оценка может измениться

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

Связанные

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

+10

Чувак, BufferedStream имеет значение. +1 :) – Marcus

+0

Значительно быстрее, чем streamReader.ReadLine только ... большое спасибо Eric.Can, вы также объясняете, почему это намного быстрее/или указывать мне на ресурс, где я могу прочитать об этом. Заранее спасибо. – techExplorer

+1

Существует стоимость запроса данных из подсистемы ввода-вывода.В случае вращения дисков вам, возможно, придется подождать, пока пластинка начнет вращаться, чтобы прочитать следующий фрагмент данных, или, что еще хуже, дождитесь, когда головка диска переместится. В то время как SSD не имеют механических частей, чтобы замедлить работу, все же стоимость доступа к IO-операциям для доступа к ним. Буферизованные потоки читают больше, чем запросы StreamReader, уменьшая количество вызовов в ОС и, в конечном счете, количество отдельных запросов ввода-вывода. –

12

Если вы читали performance and benchmark stats on this website, вы увидите, что самый быстрый способ чтения (потому что чтение, запись и обработка все разные) текстовый файл следующий фрагмент кода:

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

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

+1

Это хорошо сработало для удаления 19gg postgres файла, чтобы перевести его в sql-синтаксис в несколько файлов. Спасибо, парень postgres, который никогда не выполнил мои параметры правильно./sigh –

+0

Разница в производительности здесь, похоже, окупается для действительно больших файлов, таких как более 150 МБ (также вы действительно должны использовать 'StringBuilder' для загрузки их в память, загружается быстрее, поскольку она не создает новую строку каждый раз, когда вы добавить символы) – b729sefc

7

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

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

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

+0

У вас есть веские доказательства этого? Почему OP должен использовать это по любому другому ответу? Пожалуйста, копайте немного глубже и немного подробнее. –

0

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

0

Все отличные ответы! однако для тех, кто ищет ответ, они кажутся несколько неполными.

В качестве стандартной строки может использоваться только размер X, 2Gb до 4Gb в зависимости от вашей конфигурации, эти ответы на самом деле не отвечают требованиям OP. Один из способов заключается в работе со списком строк:

List<string> Words = new List<string>(); 

using (StreamReader sr = new StreamReader(@"C:\Temp\file.txt")) 
{ 

string line = string.Empty; 

while ((line = sr.ReadLine()) != null) 
{ 
    Words.Add(line); 
} 
} 

Некоторые хотят Tokenise и разделить строку при обработке. Список строк теперь может содержать очень большие объемы текста.

Смежные вопросы