Чтение последних n строк из файла в c/С++
Я видел много сообщений, но не нашел ничего подобного.
Я получаю неправильный вывод:
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ...... // may be this is EOF character
Переход в бесконечный цикл.
Мой алгоритм:
- Перейдите к концу файла.
- уменьшить позицию указателя на 1 и прочитать символ
персонаж.
- выйти, если мы найдем наши 10 строк или дойдем до начала файла.
- теперь я буду сканировать полный файл до EOF и печатать их//не реализован в коде.
код:
#include<iostream>
#include<stdio.h>
#include<conio.h>
#include<stdlib.h>
#include<string.h>
using namespace std;
int main()
{
FILE *f1=fopen("input.txt","r");
FILE *f2=fopen("output.txt","w");
int i,j,pos;
int count=0;
char ch;
int begin=ftell(f1);
// GO TO END OF FILE
fseek(f1,0,SEEK_END);
int end = ftell(f1);
pos=ftell(f1);
while(count<10)
{
pos=ftell(f1);
// FILE IS LESS THAN 10 LINES
if(pos<begin)
break;
ch=fgetc(f1);
if(ch=='\n')
count++;
fputc(ch,f2);
fseek(f1,pos-1,end);
}
return 0;
}
UPD 1:
измененный код: теперь он имеет только 1 ошибку - если на входе есть строки типа
3enil
2enil
1enil
it prints 10 lines only
line1
line2
line3ÿine1
line2
line3ÿine1
line2
line3ÿine1
line2
line3ÿine1
line2
PS:
1. работа над окнами в блокноте ++
-
Это не домашнее задание
-
Также я хочу сделать это, не используя больше памяти или использования STL.
-
Я тренируюсь, чтобы улучшить свои базовые знания, поэтому, пожалуйста, не сообщайте о каких-либо функциях (например, tail -5 tc.)
пожалуйста, помогите улучшить мой код.
Ответы
Ответ 1
С вашим кодом возникает ряд проблем. Большинство
важно то, что вы никогда не проверяете, что любая из функций
удалось. И сохранение результатов ftell
в int
не является
тоже очень хорошая идея. Тогда существует тест pos < begin
;
это может произойти только в случае ошибки. И тот факт, что
вы помещаете результаты fgetc
в char
(что приводит к
в случае потери информации). И тот факт, что первый читал вас
do находится в конце файла, поэтому произойдет сбой (и как только поток войдет в
состояние ошибки, оно остается там). И тот факт, что вы не можете
надежно выполнить арифметику по значениям, возвращаемым ftell
(за исключением
под Unix), если файл был открыт в текстовом режиме.
О, и нет "символа EOF"; 'ÿ'
- совершенно
character (0xFF в латинском-1). Как только вы присвоите возвращаемое значение
от fgetc
до char
, вы потеряли возможность проверить
конец файла.
Я мог бы добавить, что чтение назад по одному символу за раз
крайне неэффективен. Обычным решением было бы выделить
достаточно большой буфер, затем подсчитайте '\n'
в нем.
EDIT:
Просто немного кода, чтобы дать идею:
std::string
getLastLines( std::string const& filename, int lineCount )
{
size_t const granularity = 100 * lineCount;
std::ifstream source( filename.c_str(), std::ios_base::binary );
source.seekg( 0, std::ios_base::end );
size_t size = static_cast<size_t>( source.tellg() );
std::vector<char> buffer;
int newlineCount = 0;
while ( source
&& buffer.size() != size
&& newlineCount < lineCount ) {
buffer.resize( std::min( buffer.size() + granularity, size ) );
source.seekg( -static_cast<std::streamoff>( buffer.size() ),
std::ios_base::end );
source.read( buffer.data(), buffer.size() );
newlineCount = std::count( buffer.begin(), buffer.end(), '\n');
}
std::vector<char>::iterator start = buffer.begin();
while ( newlineCount > lineCount ) {
start = std::find( start, buffer.end(), '\n' ) + 1;
-- newlineCount;
}
std::vector<char>::iterator end = remove( start, buffer.end(), '\r' );
return std::string( start, end );
}
Это немного слабое в обработке ошибок; в частности, вы
вероятно, хотят отличить между невозможностью открыть
файл и любые другие ошибки. (Никаких других ошибок не должно быть,
но вы никогда не знаете.)
Кроме того, это чисто Windows, и он предполагает, что фактическое
файл содержит чистый текст и не содержит '\r'
, который
не являются частью CRLF. (Для Unix просто снимите
последняя строка.)
Ответ 2
Комментарии в коде
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
FILE *in, *out;
int count = 0;
long int pos;
char s[100];
in = fopen("input.txt", "r");
/* always check return of fopen */
if (in == NULL) {
perror("fopen");
exit(EXIT_FAILURE);
}
out = fopen("output.txt", "w");
if (out == NULL) {
perror("fopen");
exit(EXIT_FAILURE);
}
fseek(in, 0, SEEK_END);
pos = ftell(in);
/* Don't write each char on output.txt, just search for '\n' */
while (pos) {
fseek(in, --pos, SEEK_SET); /* seek from begin */
if (fgetc(in) == '\n') {
if (count++ == 10) break;
}
}
/* Write line by line, is faster than fputc for each char */
while (fgets(s, sizeof(s), in) != NULL) {
fprintf(out, "%s", s);
}
fclose(in);
fclose(out);
return 0;
}
Ответ 3
Это может быть сделано с использованием кругового массива очень эффективно.
Дополнительный буфер не требуется.
void printlast_n_lines(char* fileName, int n){
const int k = n;
ifstream file(fileName);
string l[k];
int size = 0 ;
while(file.good()){
getline(file, l[size%k]); //this is just circular array
cout << l[size%k] << '\n';
size++;
}
//start of circular array & size of it
int start = size > k ? (size%k) : 0 ; //this get the start of last k lines
int count = min(k, size); // no of lines to print
for(int i = 0; i< count ; i++){
cout << l[(start+i)%k] << '\n' ; // start from in between and print from start due to remainder till all counts are covered
}
}
Пожалуйста, оставьте отзыв.
Ответ 4
Я считаю, вы используете fseek
неправильно. Проверьте man fseek
на Google.
Попробуйте следующее:
fseek(f1, -2, SEEK_CUR);
//1 to neutrialize change from fgect
//and 1 to move backward
Также вы должны установить положение в начале последнего элемента:
fseek(f1, -1, SEEK_END).
Вам не нужна переменная end
.
Вы должны проверить возвращаемые значения всех функций (fgetc
, fseek
и ftell
). Это хорошая практика. Я не знаю, будет ли этот код работать с пустыми файлами или аналогичным.
Ответ 5
int end = ftell(f1);
pos=ftell(f1);
это указывает вам последнюю точку в файле, поэтому EOF.
Когда вы читаете, вы получаете ошибку EOF, и ppointer хочет переместить 1 пробел вперед...
Итак, я рекомендую уменьшить текущую позицию на единицу.
Или поставьте fseek (f1, -2, SEEK_CUR) в начале цикла while, чтобы исправить предмет на 1 пункт и перейти на 1 пункт назад...
Ответ 6
Использование: fseek(f1,-2,SEEK_CUR);
назад
Я пишу этот код, он может работать, вы можете попробовать:
#include "stdio.h"
int main()
{
int count = 0;
char * fileName = "count.c";
char * outFileName = "out11.txt";
FILE * fpIn;
FILE * fpOut;
if((fpIn = fopen(fileName,"r")) == NULL )
printf(" file %s open error\n",fileName);
if((fpOut = fopen(outFileName,"w")) == NULL )
printf(" file %s open error\n",outFileName);
fseek(fpIn,0,SEEK_END);
while(count < 10)
{
fseek(fpIn,-2,SEEK_CUR);
if(ftell(fpIn)<0L)
break;
char now = fgetc(fpIn);
printf("%c",now);
fputc(now,fpOut);
if(now == '\n')
++count;
}
fclose(fpIn);
fclose(fpOut);
}
Ответ 7
Я бы использовал два потока для печати последних n строк файла:
Это выполняется в O (строки) времени выполнения и O (строки).
#include<bits/stdc++.h>
using namespace std;
int main(){
// read last n lines of a file
ifstream f("file.in");
ifstream g("file.in");
// move f stream n lines down.
int n;
cin >> n;
string line;
for(int i=0; i<k; ++i) getline(f,line);
// move f and g stream at the same pace.
for(; getline(f,line); ){
getline(g, line);
}
// g now has to go the last n lines.
for(; getline(g,line); )
cout << line << endl;
}
Решение с O (строками) runtime и O (N) пространство использует очередь:
ifstream fin("file.in");
int k;
cin >> k;
queue<string> Q;
string line;
for(; getline(fin, line); ){
if(Q.size() == k){
Q.pop();
}
Q.push(line);
}
while(!Q.empty()){
cout << Q.front() << endl;
Q.pop();
}
Ответ 8
Вот решение в C++.
#include <iostream>
#include <string>
#include <exception>
#include <cstdlib>
int main(int argc, char *argv[])
{
auto& file = std::cin;
int n = 5;
if (argc > 1) {
try {
n = std::stoi(argv[1]);
} catch (std::exception& e) {
std::cout << "Error: argument must be an int" << std::endl;
std::exit(EXIT_FAILURE);
}
}
file.seekg(0, file.end);
n = n + 1; // Add one so the loop stops at the newline above
while (file.tellg() != 0 && n) {
file.seekg(-1, file.cur);
if (file.peek() == '\n')
n--;
}
if (file.peek() == '\n') // If we stop in the middle we will be at a newline
file.seekg(1, file.cur);
std::string line;
while (std::getline(file, line))
std::cout << line << std::endl;
std::exit(EXIT_SUCCESS);
}
Телосложение:
$ g++ <SOURCE_NAME> -o last_n_lines
Бежать:
$ ./last_n_lines 10 < <SOME_FILE>