Прочитать последнюю строчку файла

Обновлено: 07.07.2024

Доступ к диску (чтение/запись) гораздо (на несколько порядков) медленнее, чем доступ к данным в оперативной памяти. Кроме того, если мы читаем или записываем файл при помощи системных вызовов маленькими порциями (по 1-10 символов)

то мы проигрываем еще в одном: каждый системный вызов - это обращение к ядру операционной системы. При каждом таком обращении происходит довольно большая дополнительная работа (смотри главу "Взаимодействие с UNIX"). При этом накладные расходы на такое посимвольное чтение файла могут значительно превысить полезную работу.

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

Для решения этих двух проблем была построена специальная библиотека функций, названная stdio - "стандартная библиотека ввода/вывода" (standard input/output library). Она является частью библиотеки /lib/libc.a и представляет собой надстройку над системными вызовами (т.к. в конце концов все ее функции время от времени обращаются к системе, но гораздо реже, чем если использовать сисвызовы непосредственно).

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

  • дескриптор fd файла для обращения к системным вызовам;
  • указатель на буфер, размещенный в памяти программы;
  • указатель на текущее место в буфере, откуда надо выдать или куда записать очередной символ; этот указатель продвигается при каждом вызове getc или putc;
  • счетчик оставшихся в буфере символов (при чтении) или свободного места (при записи);
  • режимы открытия файла (чтение/запись/чтение+запись) и текущее состояние файла. Одно из состояний - при чтении файла был достигнут его конец **;
  • способ буферизации;

Предусмотрено несколько стандартных структур FILE, указатели на которые называются stdin, stdout и stderr и связаны с дескрипторами 0, 1, 2 соответственно (стандартный ввод, стандартный вывод, стандартный вывод ошибок). Напомним, что эти каналы открыты неявно (автоматически) и, если не перенаправлены, связаны с вводом с клавиатуры и выводом на терминал.

Буфер в оперативной памяти нашей программы создается (функцией malloc) при открытии файла при помощи функции fopen(). После открытия файла все операции обмена с файлом происходят не по 1 байту, а большими порциями размером с буфер - обычно по 512 байт (константа BUFSIZ).

При чтении символа getc выдает ее первый байт.

При последующих вызовах getc выдаются следующие байты из буфера, а обращений к диску уже не происходит! Лишь когда буфер будет исчерпан - произойдет очередное чтение с диска. Таким образом, информация читается из файла с опережением, заранее наполняя буфер; а по требованию выдается уже из буфера. Если мы читаем 1024 байта из файла при помощи getc(), то мы 1024 раза вызываем эту функцию, но всего 2 раза системный вызов read - для чтения двух порций информации из файла, каждая - по 512 байт.

  • буфер заполнен (содержит BUFSIZ символов).
  • при закрытии файла (fclose или exit ***).
  • при вызове функции fflush (см. ниже).
  • в специальном режиме - после помещения в буфер символа '\n' (см. ниже).
  • в некоторых версиях - перед любой операцией чтения из канала stdin (например, при вызове gets), при условии, что stdout буферизован построчно (режим _IOLBF, смотри ниже), что по-умолчанию так и есть.

Приведем упрощенную схему, поясняющую взаимоотношения основных функций и макросов из stdio (кто кого вызывает). Далее s означает строку, c - символ, fp - указатель на структуру FILE **** . Функции, работающие со строками, в цикле вызывают посимвольные операции. Обратите внимание, что в конце концов все функции обращаются к системным вызовам read и write, осуществляющим ввод/вывод низкого уровня.

Системные вызовы далее обозначены жирно, макросы - курсивом.

Открыть файл, создать буфер: По умолчанию fopen() использует для creat коды доступа accessmode равные 0666 (rwrw-rw-).

Соответствие аргументов fopen и open:

Для r, r+ файл уже должен существовать, в остальных случаях файл создается, если его не было.

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

Функции _flsbuf и _filbuf - внутренние для stdio, они как раз сбрасывают буфер в файл либо читают новый буфер из файла.

По указателю fp можно узнать дескриптор файла: Это макроопределение просто выдает поле из структуры FILE. Обратно, если мы открыли файл open-ом, мы можем ввести буферизацию этого канала: (здесь надо вновь указать КАК мы открываем файл, что должно соответствовать режиму открытия open-ом). Теперь можно работать с файлом через fp, а не fd.

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

Функция ungetc(c,fp) "возвращает" прочитанный байт в файл. На самом деле байт возвращается в буфер, поэтому эта операция неприменима к небуферизованным каналам. Возврат соответствует сдвигу указателя чтения из буфера (который увеличивается при getc()) на 1 позицию назад. Вернуть можно только один символ подряд (т.е. перед следующим ungetc-ом должен быть хоть один getc), поскольку в противном случае можно сдвинуть указатель за начало буфера и, записывая туда символ c, разрушить память программы.

Очень часто делают ошибку в функции fputc, путая порядок ее аргументов. Так ничего не стоит написать: Запомните навсегда! указатель файла идет вторым! Существует также макроопределение Оно ведет себя как и функция fputc, но не может быть передано в качестве аргумента в функцию:

Тем не менее всегда, где возможно, следует пользоваться макросом - он работает быстрее. Аналогично, есть функция fgetc(fp) и макрос getc(fp).

Отметим еще, что putchar и getchar это тоже всего лишь макросы

Известная вам функция printf также является частью библиотеки stdio. Она входит в семейство функций:

Первая из функций форматирует свои аргументы в соответствии с форматом, заданным строкой fmt (она содержит форматы в виде %-ов) и записывает строку-результат посимвольно (вызывая putc) в файл fp. Вторая - это всего-навсего fprintf с каналом fp равным stdout. Третяя выдает сформатированную строку не в файл, а записывает ее в массив bf. В конце строки sprintf добавляет нулевой байт '\0' - признак конца.

Для чтения данных по формату используются функции семейства

Функции fprintf и fscanf являются наиболее мощным средством работы с текстовыми файлами (содержащими изображение данных в виде печатных символов).

Текстовые файлы (имеющие строчную организацию) хранятся на диске как линейные массивы байт. Для разделения строк в них используется символ '\n'. Так, например, текст хранится как массив

При выводе на экран дисплея символ \n преобразуется драйвером терминалов в последовательность \r\n, которая возвращает курсор в начало строки ('\r') и опускает курсор на строку вниз ('\n'), то есть курсор переходит в начало следующей строки.

В MS DOS строки в файле на диске разделяются двумя символами \r\n и при выводе на экран никаких преобразований не делается ***** . Зато библиотечные функции языка Си преобразуют эту последовательность при чтении из файла в \n, а при записи в файл превращают \n в \r\n, поскольку в Си считается, что строки разделяются только \n. Для работы с файлом без таких преобразований, его надо открывать как "бинарный":

Все нетекстовые файлы в MS DOS надо открывать именно так, иначе могут произойти разные неприятности. Например, если мы программой копируем нетекстовый файл в текстовом режиме, то одиночный символ \n будет считан в программу как \n, но записан в новый файл как пара \r\n. Поэтому новый файл будет отличаться от оригинала (что для файлов с данными и программ совершенно недопустимо!).

Задание: напишите программу подсчета строк и символов в файле. Указание: надо подсчитать число символов '\n' в файле и учесть, что последняя строка файла может не иметь этого символа на конце. Поэтому если последний символ файла (тот, который вы прочитаете самым последним) не есть '\n', то добавьте к счетчику строк 1.

Напишите программу подсчета количества вхождений каждого из символов алфавита в файл и печатающую результат в виде таблицы в 4 колонки. (Указание: заведите массив из 256 счетчиков. Для больших файлов счетчики должны быть типа long).

Почему вводимый при помощи функций getchar() и getc(fp) символ должен описываться типом int а не char?

  • Пусть ch имеет тип unsigned char. Тогда ch всегда лежит в интервале 0. 255 и НИКОГДА не будет равно (-1). Даже если getchar() вернет такое значение, оно будет приведено к типу unsigned char обрубанием и станет равным 255. При сравнении с целым (-1) оно расширится в int добавлением нулей слева и станет равно 255. Таким образом, наша программа никогда не завершится, т.к. вместо признака конца файла она будет читать символ с кодом 255 (255 != -1).
  • Пусть ch имеет тип signed char. Тогда перед сравнением с целым числом EOF байт ch будет приведен к типу signed int при помощи расширения знакового бита (7ого). Если getchar вернет значение (-1), то оно будет сначала в присваивании значения байту ch обрублено до типа char: 255; но в сравнении с EOF значение 255 будет приведено к типу int и получится (-1). Таким образом, истинный конец файла будет обнаружен. Но теперь, если из файла будет прочитан настоящий символ с кодом 255, он будет приведен в сравнении к целому значению (-1) и будет также воспринят как конец файла. Таким образом, если в нашем файле окажется символ с кодом 255, то программа воспримет его как фальшивый конец файла и оставит весь остаток файла необработанным (а в нетекстовых файлах такие символы - не редкость).
  • Пусть ch имеет тип int или unsigned int (больше 8 бит). Тогда все корректно.

Отметим, что в UNIX признак конца файла в самом файле физически НЕ ХРАНИТСЯ. Система в любой момент времени знает длину файла с точностью до одного байта; признак EOF вырабатывается стандартными функциями тогда, когда обнаруживается, что указатель чтения достиг конца файла (то есть позиция чтения стала равной длине файла - последний байт уже прочитан).

В MS DOS же в текстовых файлах признак конца (EOF) хранится явно и обозначается символом CTRL/Z. Поэтому, если программным путем записать куда-нибудь в середину файла символ CTRL/Z, то некоторые программы перестанут "видеть" остаток файла после этого символа!

Наконец отметим, что разные функции при достижении конца файла выдают разные значения: scanf, fscanf, fgetc, getc, getchar выдают EOF, read - выдает 0, а gets, fgets - NULL.

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

Ответ: функция gets() читает строку (завершающуюся '\n') из канала fp==stdin. Она не контролирует длину буфера, в которую считывается строка, поэтому если строка окажется слишком длинной - ваша программа повредит свою память (и аварийно завершится). Единственный возможный совет - делайте буфер достаточно большим (очень туманное понятие!), чтобы вместить максимально возможную (длинную) строку.

Функция fgets() контролирует длину строки: если строка на входе окажется длиннее, чем slen символов, то остаток строки не будет прочитан в буфер s, а будет оставлен "на потом". Следующий вызов fgets прочитает этот сохраненный остаток. Кроме того fgets, в отличие от gets, не обрубает символ '\n' на конце строки, что доставляет нам дополнительные хлопоты по его уничтожению, поскольку в Си "нормальные" строки завершаются просто '\0', а не "\n\0".

Здесь len - длина строки. Если бы мы выбросили оператор, помеченный '@', то printf печатал бы текст через строку, поскольку выдавал бы код '\n' дважды - из строки buffer и из формата "%s\n".

Если в файле больше нет строк (файл дочитан до конца), то функции gets и fgets возвращают значение NULL. Обратите внимание, что NULL, а не EOF. Пока файл не дочитан, эти функции возвращают свой первый аргумент - адрес буфера, в который была записана очередная строка файла.

Фрагмент для обрубания символа перевода строки может выглядеть еще так:

В чем отличие puts(s); и fputs(s,fp); ?

Ответ: puts выдает строку s в канал stdout. При этом puts выдает сначала строку s, а затем - дополнительно - символ перевода строки '\n'. Функция же fputs символ перевода строки не добавляет. Упрощенно:

Найдите ошибки в программе: Мораль: надо быть внимательнее к формату вызова и смыслу библиотечных функций.

* Это не та "связующая" структура file в ядре, про которую шла речь выше, а ЕЩЕ одна - в памяти самой программы.

** Проверить это состояние позволяет макрос feof(fp); он истинен, если конец был достигнут, ложен - если еще нет.

*** При выполнении вызова завершения программы exit(); все открытые файлы автоматически закрываются.

**** Обозначения fd для дескрипторов и fp для указателей на файл прижились и их следует придерживаться. Если переменная должна иметь более мнемоничное имя - следует писать так: fp_output, fd_input (а не просто fin, fout).

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

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

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

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

перейти к концу, и начать чтение блоков назад, пока вы не найдете все ваши критерии для линии. Если последний блок не" заканчивается " строкой, вам, вероятно, придется попробовать сканировать вперед (предполагая, что действительно длинная строка в активно добавляемом файле).

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

хотя ответ derpface определенно правильный, он часто возвращает неожиданные результаты. Причина этого в том, что, по крайней мере, в моей операционной системе (Mac OSX 10.9.5), многие текстовые редакторы завершают свои файлы символом "end line".

например, когда я открываю vim, введите только один символ "a" (без возврата) и сохраните, файл теперь будет содержать (в шестнадцатеричном формате):

где 61-буква "a" , а 0A-конец строки характер.

это означает, что код derpface вернет пустую строку для всех файлов, созданных таким текстовым редактором.

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

мой код для игнорирования последнего символа входной файл:

что будет на выходе:

в одном файле "a".

Я также боролся с проблемой, потому что я запустил код убервулу, а также получил пустую строку. Вот что я нашел. Я использую следующее .CSV-файл в качестве примера:

чтобы понять команды в коде, обратите внимание на следующие местоположения и их соответствующие символы. (Loc, char) : . (63,'3') , (64,'5') , (65,-) , (66,'\n'), (EOF, -).

первоначально это было предназначено для чтения последней записи системного журнала. Учитывая, что последний символ перед EOF '\n' мы ищем, чтобы найти следующее вхождение '\n' и затем мы храним строку в строку.

Какой самый быстрый и эффективный способ чтения последней строки текста из [очень, очень большого] файла в Java?

В общем, это не очень-то просто. Как указывает MSalter, UTF-8 позволяет легко обнаружить \r или \n , поскольку представление этих символов в UTF-8 такое же, как ASCII, и эти байты не будут -байтный символ.

Итак, в основном, возьмите буфер размером (скажем) 2 КБ и постепенно читайте в обратном направлении (переходите к 2 КБ до того, как вы были раньше, прочитайте следующие 2 КБ), проверяя завершение строки. Затем перейдите в нужное место в потоке, создайте InputStreamReader вверху и BufferedReader поверх него. Тогда просто позвоните BufferedReader.readLine() .

Ниже приведены две функции, одна из которых возвращает последнюю непустую строку файла без загрузки или пошагового просмотра всего файла, а другая, которая возвращает последние N строк файла без пошагового просмотра всего файла:

Что делает хвост, так это приближается к последнему символу файла, затем шагает назад, символ за символом, записывая то, что он видит, пока не найдет разрыв строки. Как только он находит разрыв строки, он выходит из цикла. Изменяет то, что было записано, превращает это в строку и возвращает. 0xA - это новая строка, а 0xD - возврат каретки.

Если ваши строки заканчиваются на \r\n или crlf или какой-либо другой «двойной символ новой строки в стиле новой строки», тогда вам нужно будет указать n * 2 строк, чтобы получить последние n строк, потому что он учитывает 2 строки для каждой строки. .

Но вам, вероятно, не нужна последняя строка, вам нужны последние N строк, поэтому используйте вместо этого:

Вызовите вышеуказанные методы следующим образом:

Предупреждение На диком западе юникода этот код может привести к неправильному выводу этой функции. Например, "Мэри?" Вместо "Мэри". Символы с шляпами, диакритическими знаками, китайскими иероглифами и т. Д. Могут привести к неправильному выводу из-за добавления диакритических знаков. как модификаторы после символа. Обращение составных символов изменяет характер личности при перевороте. Вам нужно будет провести полную батарею тестов на всех языках, с которыми вы планируете использовать это.

Переход в бесконечный цикл.

  • Перейдите к концу файла.
  • уменьшить позицию указателя на 1 и прочитать символ персонаж.
  • выйти, если мы найдем наши 10 строк или дойдем до начала файла.
  • теперь я буду сканировать полный файл до EOF и печатать их//не реализован в коде.

измененный код: теперь он имеет только 1 ошибку - если на входе есть строки типа

PS:
1. работа над окнами в блокноте ++

Это не домашнее задание

Также я хочу сделать это, не используя больше памяти или использования STL.

Я тренируюсь, чтобы улучшить свои базовые знания, поэтому, пожалуйста, не сообщайте о каких-либо функциях (например, tail -5 tc.)

пожалуйста, помогите улучшить мой код.

ОТВЕТЫ

Ответ 1

С вашим кодом возникает ряд проблем. Большинство важно то, что вы никогда не проверяете, что любая из функций удалось. И сохранение результатов ftell в int не является тоже очень хорошая идея. Тогда существует тест pos < begin ; это может произойти только в случае ошибки. И тот факт, что вы помещаете результаты fgetc в char (что приводит к в случае потери информации). И тот факт, что первый читал вас do находится в конце файла, поэтому произойдет сбой (и как только поток войдет в состояние ошибки, оно остается там). И тот факт, что вы не можете надежно выполнить арифметику по значениям, возвращаемым ftell (за исключением под Unix), если файл был открыт в текстовом режиме.

О, и нет "символа EOF"; 'ÿ' - совершенно character (0xFF в латинском-1). Как только вы присвоите возвращаемое значение от fgetc до char , вы потеряли возможность проверить конец файла.

Я мог бы добавить, что чтение назад по одному символу за раз крайне неэффективен. Обычным решением было бы выделить достаточно большой буфер, затем подсчитайте '\n' в нем.

Просто немного кода, чтобы дать идею:

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

Кроме того, это чисто Windows, и он предполагает, что фактическое файл содержит чистый текст и не содержит '\r' , который не являются частью CRLF. (Для Unix просто снимите последняя строка.)

Ответ 2

Комментарии в коде

Ответ 3

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

Пожалуйста, оставьте отзыв.

Ответ 4

Я считаю, вы используете fseek неправильно. Проверьте man fseek на Google.

Также вы должны установить положение в начале последнего элемента:

Вам не нужна переменная end .

Вы должны проверить возвращаемые значения всех функций ( fgetc , fseek и ftell ). Это хорошая практика. Я не знаю, будет ли этот код работать с пустыми файлами или аналогичным.

Ответ 5

это указывает вам последнюю точку в файле, поэтому EOF. Когда вы читаете, вы получаете ошибку EOF, и ppointer хочет переместить 1 пробел вперед.

Ответ 6

Использование: fseek(f1,-2,SEEK_CUR); назад

Я пишу этот код, он может работать, вы можете попробовать:

Ответ 7

Я бы использовал два потока для печати последних n строк файла: Это выполняется в O (строки) времени выполнения и O (строки).

Решение с O (строками) runtime и O (N) пространство использует очередь:

Читайте также: