C: Чтение текстового файла (со строками переменной длины) построчно с использованием fread () / fgets () вместо fgetc () (блок ввода-вывода против символьного ввода-вывода) - PullRequest
6 голосов
/ 10 декабря 2010

Существует ли функция getline, которая использует fread (блок ввода / вывода) вместо fgetc (символьный ввод / вывод)?

Производительность снижается при чтении файла символ за символом через fgetc. Мы думаем, что для повышения производительности мы можем использовать чтение блоков через fread во внутреннем цикле getline. Тем не менее, это приводит к потенциально нежелательному эффекту чтения после конца строки. По крайней мере, для этого потребуется реализация getline для отслеживания «непрочитанной» части файла, что требует абстракции, выходящей за пределы семантики ANSI C FILE. Это не то, что мы хотим реализовать сами!

Мы профилировали наше приложение, и низкая производительность изолирована от того факта, что мы потребляем большие файлы символ за символом через fgetc. Для сравнения, остальная часть накладных расходов имеет тривиальную стоимость. Мы всегда последовательно читаем каждую строку файла, от начала до конца, и мы можем заблокировать весь файл на время чтения. Это, вероятно, облегчает реализацию fread на основе *1013*.

Итак, существует ли функция getline, которая использует fread (блочный ввод / вывод) вместо fgetc (символьный ввод / вывод)? Мы уверены, что это так, но если нет, то как мы должны это реализовать?

Обновление Найдена полезная статья, Обработка пользовательского ввода в C , автор Paul Hsieh. Это подход, основанный на fgetc, но в нем есть интересное обсуждение альтернатив (начиная с того, насколько плох gets, затем обсуждая fgets):

С другой стороны, обычная реплика программистов на С (даже тех, кого считают опытными) состоит в том, что fgets () следует использовать в качестве альтернативы. Конечно, само по себе fgets () на самом деле не обрабатывает пользовательский ввод как таковой. Помимо наличия причудливого условия завершения строки (при обнаружении \ n или EOF, но не \ 0), механизм, выбранный для завершения, когда буфер достиг емкости, состоит в том, чтобы просто внезапно остановить операцию fgets () и \ 0 прекратить это. Поэтому, если пользовательский ввод превышает длину предварительно выделенного буфера, fgets () возвращает частичный результат. Чтобы справиться с этим у программистов есть пара вариантов; 1) просто иметь дело с усеченным пользовательским вводом (нет способа сообщить пользователю, что ввод был усечен, пока он обеспечивает ввод) 2) имитировать растущий массив символов и заполнять его последовательными вызовами fgets () . Первое решение - почти всегда очень плохое решение для пользовательского ввода переменной длины, потому что большую часть времени буфер неизбежно будет слишком большим, потому что он пытается захватить слишком много обычных случаев, и слишком мал для необычных случаев. Второе решение хорошо, за исключением того, что оно может быть сложным для правильной реализации. Ни один из них не имеет дело с fgets ' странным поведением относительно' \ 0 '.

Упражнение, оставленное читателю: чтобы определить, сколько байтов действительно было прочитано при вызове fgets () , можно попробовать сканировать, как это делается, для \ n 'и пропустите любой символ' \ 0 ', не превышая размер, переданный fgets () . Объясните, почему этого недостаточно для самой последней строки потока. Какая слабость ftell () мешает ему полностью решить эту проблему?

Упражнение, оставленное читателю: Решите проблему определения длины данных, потребляемых fgets () , перезаписывая весь буфер ненулевым значением между каждым вызовом fgets () .

Таким образом, с fgets () у нас остается выбор написать много кода и жить с условием завершения строки, которое несовместимо с остальной частью библиотеки C, или с произвольным выкл. Если это не достаточно хорошо, то с чем мы остаемся? scanf () смешивает разбор с чтением таким образом, который не может быть разделен, и fread () будет читать после конца строки. Короче говоря, библиотека C не оставляет нас ни с чем. Мы вынуждены бросить свои собственные, основываясь на вершине fgetc () напрямую. Итак, давайте попробуем.

Итак, существует ли getline функция, основанная на fgets (и не усекающая ввод)?

Ответы [ 2 ]

5 голосов
/ 10 декабря 2010

Не используйте fread.Используйте fgets.Я так понимаю, это проблема с домашним заданием / проектом класса, поэтому я не даю полного ответа, но если вы скажете, что нет, я дам больше советов.Определенно возможно обеспечить 100% семантики в стиле GNU getline, включая встроенные нулевые байты, используя чисто fgets, но это требует некоторого умного мышления.

ОК, обновите, так как это не 't домашнее задание:

  • memset ваш буфер для '\n'.
  • Используйте fgets.
  • Используйте memchr, чтобы найти первый '\n'.
  • Если '\n' не найдено, строка длиннее вашего буфера.Увеличьте буфер, заполните новую часть с помощью '\n' и fgets в новой части, повторяя при необходимости.
  • Если символ, следующий за '\n', равен '\0', то fgets завершается из-задо достижения конца строки.
  • В противном случае fgets завершается из-за достижения EOF, '\n' остается от вашего memset, предыдущий символ является завершающим нулем, который fgets написал,и символ перед этим является последним символом фактического чтения данных.

Вы можете исключить memset и использовать strlen вместо memchr, если вам не нужны вспомогательные строкисо встроенными нулями (в любом случае, нулевое значение не прекратит чтение; оно будет просто частью вашей строки для чтения).

Есть также способ сделать то же самое с fscanf и "%123[^\n]" спецификатор (где 123 - ваш буферный лимит), который дает вам возможность останавливаться на символах, не являющихся символом новой строки (аля GNU getdelim).Однако это, вероятно, медленно, если ваша система не имеет очень причудливой реализации scanf.

1 голос
/ 11 декабря 2010

Между fgets и fgetc / setvbuf нет большой разницы в производительности. Попробуйте:

int c;
FILE *f = fopen("blah.txt","r");
setvbuf(f,NULL,_IOLBF,4096); /* !!! check other values for last parameter in your OS */
while( (c=fgetc(f))!=EOF )
{
  if( c=='\n' )
    ...
  else
    ...
} 
...