2013-07-26 19 views
10

Ho visto molti post ma non ho trovato nulla di simile.
sto ottenendo uscita sbagliata:leggendo le ultime n righe dal file in c/C++

ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ...... // may be this is EOF character 

Entrando nel ciclo infinito.

mio algoritmo:

  1. Vai alla fine del file.
  2. diminuire la posizione del puntatore di 1 e leggere il carattere con il carattere .
  3. uscita se abbiamo trovato le nostre 10 linee o raggiungiamo l'inizio del file.
  4. ora eseguirò la scansione del file completo fino a EOF e stamperò // non implementato nel codice. codice

:

#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:

cambiato codice: ha solo 1 errore ora - se l'ingresso ha linee come

3enil 
2enil 
1enil 

it prints 10 lines only 

line1 
line2 
line3ÿine1 
line2 
line3ÿine1 
line2 
line3ÿine1 
line2 
line3ÿine1 
line2 

PS:
1. lavorando su Windows nel blocco note ++

  1. questo non è compito

  2. anche io voglio farlo senza utilizzare alcun più memoria o l'uso di STL.

  3. sto esercitando per migliorare la mia conoscenza di base quindi per favore non postare su eventuali funzioni (come la coda -5 tc.)

si prega di contribuire a migliorare il mio codice.

+8

Suggerimento: 'progressi fgetc' il file indicatore di posizione per uno. – jsalonen

+1

prova 'fseek (f1, pos-1, SEEK_SET);' e bin file. – BLUEPIXY

+4

C o C++? Sceglierne uno. (Suggerimento: è principalmente C.) –

risposta

7

Ci sono una serie di problemi con il tuo codice. Il più importante è che non si verifica mai nessuna delle funzioni riuscita. E il salvataggio dei risultati è uno in un int non è uno una buona idea neanche. Poi c'è il test pos < begin; questo può accadere solo se c'è stato un errore. E il fatto che stai mettendo i risultati di fgetc in un char (che risulta in una perdita di informazioni). E il fatto che il primo che hai letto è do è alla fine del file, quindi fallirà (e una volta che uno stream entra nello stato di errore, rimane lì). E il fatto che non sia possibile fare in modo affidabile l'aritmetica sui valori restituiti da ftell (eccetto sotto Unix) se il file è stato aperto in modalità testo.

Oh, e non esiste un "carattere EOF"; 'ÿ' è un carattere perfettamente valido (0xFF in Latin-1). Dopo aver assegnato il valore restituito di fgetc a char, hai perso qualsiasi possibilità di testare per la fine del file .

Potrei aggiungere che la lettura all'indietro di un carattere alla volta è estremamente inefficiente. La soluzione usuale sarebbe quella di allocare un buffer sufficientemente grande, quindi contare lo '\n' in esso.

EDIT:

Solo una breve po 'di codice per dare l'idea:

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); 
} 

Questo è un po' debole nella gestione degli errori; in particolare, è preferibile che tu voglia distinguere tra l'impossibilità di aprire un file e qualsiasi altro errore tra e . (Non ci sono altri errori dovrebbero verificano, ma non si sa mai.)

Inoltre, questo è puramente di Windows, e suppone che il file effettivo contiene testo puro, e non contiene alcun '\r' che aren' t parte di un CRLF. (Per Unix, è sufficiente rilasciare l'ultima riga successiva alla riga .)

+0

Sto solo programmando la pratica e voglio leggere un file all'indietro. non è per alcuno scopo di efficienza.è solo per essere sicuro di gestire i file. e ho imparato molto di cui non ero a conoscenza. –

+1

Bene, l'importante è sempre controllare gli errori prima di usare i risultati della lettura (che non ho fatto nel mio codice di esempio), che 'fgetc' (e' istream :: get() ') restituiscono un' int ', non un' char' per restituire una EOF fuori banda (che viene anche usata per errori), e il fatto che qualsiasi condizione di errore sia appiccicosa: se vedi un errore, devi cancellarlo prima di ogni altra operazione sullo stream può funzionare. E se stai lavorando in C++, starai molto meglio imparando iostream, in quanto è molto più flessibile e sicuro. –

+0

come hai indicato "ftell in un int non è neanche una buona idea". pos = ftell (pos); se (pos <0) si interrompe; non funziona. quindi cosa si può fare in c. proverò C++ in seguito. –

0

Usa: fseek(f1,-2,SEEK_CUR); to back

scrivo questo codice, Può funzionare, si può provare:

#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); 
} 
+0

dovrebbe essere fseek (f1, -2, SEEK_CUR); –

+0

@anon È perché usiamo widechar? –

+0

@anon Sì, hai ragione, ma perché? Puoi dirmelo? Grazie. –

1

credo, che si sta utilizzando fseek sbagliato. Controlla man fseek su Google.

Prova questo:

fseek(f1, -2, SEEK_CUR); 
//1 to neutrialize change from fgect 
//and 1 to move backward 

Inoltre si dovrebbe impostare la posizione all'inizio per l'ultimo elemento:

fseek(f1, -1, SEEK_END). 

Non è necessario end variabile.

È necessario verificare i valori di restituzione di tutte le funzioni (fgetc, fseek e ftell). È una buona pratica Non so se questo codice funzionerà con file vuoti o sth simili.

+0

$ man fseek 'man' non è riconosciuto come comando interno o esterno, programma eseguibile o file batch. – Default

+0

@Default usa linux o Internet – Ari

+0

@Ari Il poster originale affermava esplicitamente che era sotto Windows. (E anche sotto Unix, consiglierei di andare sullo standard Posix, piuttosto che su 'man', se sei interessato alla portabilità. Anche se molte pagine man specificano ciò che è standard e cos'è l'estensione.) –

1
int end = ftell(f1); 
pos=ftell(f1); 

questo indica l'ultimo punto nel file, quindi EOF. Quando leggi, ottieni l'errore EOF e il ppointer vuole spostarsi di 1 spazio in avanti ...

Quindi, consiglio di diminuire la posizione corrente di uno. o mettere la fseek (f1, -2, SEEK_CUR) all'inizio del ciclo while per compensare la fread di 1 punto e andare 1 punto indietro ...

8

commenti nel codice

#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; 
} 
+0

il tuo è una buona implementazione, ma voglio sapere funzionerà (pos) se ho molto molto grande no. di righe e caratteri.say file di circa 20-30 GB –

+0

Sembra che tu sia su Windows e 20-30 GB può essere un problema, usa '_fseeki64' e' _ftelli64' che supportano offset di file più lunghi anche su Windows a 32 bit –

2

Questa operazione può essere eseguita utilizzando array circolare in modo molto efficiente. Non è richiesto alcun buffer aggiuntivo.

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 
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] << endl ; // start from inbetween and print from start due to remainder till all counts are covered 
} 

} 

Si prega di fornire un feedback

0

userei due flussi per stampare ultime n righe del file: Questo viene eseguito in O (righe) runtime e O (linee) spazio.

#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; 
} 

Una soluzione con un O (N) spazioO (righe) runtime e sta usando una coda:

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(); 
} 
Problemi correlati