2010-12-10 7 views
6

Esiste un getline funzione che utilizza fread (blocco I/O) anziché fgetc (I/O carattere)?C: Lettura di un file di testo (con righe di lunghezza variabile) riga per riga utilizzando fread()/fgets() anziché fgetc() (blocco I/O vs carattere I/O)

C'è una penalità di prestazioni nella lettura di un file carattere per carattere tramite fgetc. Riteniamo che per migliorare le prestazioni, possiamo utilizzare le letture di blocco tramite fread nel ciclo interno di getline. Tuttavia, questo introduce l'effetto potenzialmente indesiderato della lettura oltre la fine di una linea. Per lo meno, ciò richiederebbe l'implementazione di getline per tenere traccia della parte "non letta" del file, che richiede un'astrazione oltre la semantica ANSI C FILE. Questo non è qualcosa che vogliamo implementare noi stessi!

Abbiamo profilato la nostra applicazione e le prestazioni lente sono isolate dal fatto che stiamo consumando file di grandi dimensioni carattere per carattere tramite fgetc. Il resto del sovraccarico in realtà ha un costo insignificante al confronto. Stiamo sempre leggendo sequenzialmente ogni riga del file, dall'inizio alla fine, e possiamo bloccare l'intero file per la durata della lettura. Ciò probabilmente rende più semplice l'implementazione di fread basata su getline.

Quindi, esiste una funzione getline che utilizza fread (blocco I/O) anziché fgetc (I/O carattere)? Siamo abbastanza sicuri che lo faccia, ma in caso contrario, come dovremmo implementarlo?

Aggiornamento trovato un articolo utile, Handling User Input in C, da Paul Hsieh. E 'un approccio basata su fgetc, ma ha una interessante discussione delle alternative (a partire da quanto male gets è, quindi discutere fgets):

D'altra parte la storta comune da programmatori C (anche quelli considerati esperti) è per dire che fgets() dovrebbe essere usato come alternativa. Naturalmente, da solo, fgets() non gestisce realmente l'input dell'utente di per sé. Oltre ad avere una bizzarra condizione di terminazione di stringa (incontrando \ n o EOF, ma non \ 0) il meccanismo scelto per la terminazione quando il buffer ha raggiunto la capacità è interrompere bruscamente l'operazione fgets() e \ 0 terminarla. Quindi, se l'input dell'utente supera la lunghezza del buffer preallocato, fgets() restituisce un risultato parziale. Affrontare questo programmatore ha un paio di scelte; 1) tratta semplicemente l'input dell'utente troncato (non c'è modo di restituire all'utente che l'input è stato troncato, mentre forniscono input) 2) Simulare un array di caratteri espandibile e riempirlo con chiamate successive a fgets (). La prima soluzione è quasi sempre una soluzione molto scarsa per l'input dell'utente di lunghezza variabile poiché il buffer sarà inevitabilmente troppo grande la maggior parte del tempo perché tenta di catturare troppi casi ordinari e troppo piccolo per casi insoliti. La seconda soluzione va bene, tranne che può essere complicata da implementare correttamente. Né si occupa del comportamento dispari di flip rispetto a '\ 0'.

esercizio lasciato al lettore: Al fine di determinare il numero di byte è stato veramente letto da una chiamata a fgets(), si potrebbe provare con la scansione, così come avviene, per un '\ n' e saltare su qualsiasi '\ 0' senza superare la dimensione passata a fgets(). Spiega perché questo non è sufficiente per l'ultima riga di un flusso.Quale punto debole di ftell() impedisce di risolvere completamente questo problema?

esercizio lasciato al lettore: risolvere il problema nel determinare la lunghezza dei dati consumati da fgets() sovrascrivendo l'intero buffer con un valore diverso da zero tra ogni chiamata fgets().

Quindi, con fgets() ci siamo lasciati con la scelta di scrivere un sacco di codice e di vivere con una condizione di terminazione di linea, che è in contrasto con il resto della libreria C, o che ha un arbitrario cut-off. Se questo non è abbastanza buono, allora cosa ci rimane? scanf() mischia l'analisi con la lettura in un modo che non può essere separato e fread() leggerà oltre la fine della stringa. In breve, la libreria C ci lascia senza niente. Siamo costretti a rollare il nostro basato su fgetc() direttamente. Quindi diamo un colpo.

Quindi, fa una funzione getline che si basa su fgets (e non tronca l'ingresso) esiste?

+0

Alla tua nuova domanda alla fine, sì, esiste. L'ho delineato nella mia risposta. L'articolo che hai citato menziona un problema con una linea finale non di nuova riga; Ho reso questo un non-problema pre-compilando il buffer con ''\ n'' e fornendo un modo per rilevare la condizione. –

+1

Si noti inoltre che la soluzione di Paul Hsieh per usare 'fgetc' è pessima. Nelle implementazioni moderne, a causa della necessità di supportare il blocco nel caso in cui più thread accedano allo stesso oggetto 'FILE', l'uso di' fgetc' sarà molto lento. Puoi usare 'getc_unlocked' (ma questa è una funzione POSIX, non una funzione C standard), ma anche con un'espansione macro ottimale di' getc_unlocked', il modo in cui 'fgets' cerca nel buffer '\ n'' (cioè usando 'memchr') sarà molte volte più veloce di qualsiasi cosa tu possa fare senza accedere al buffer interno. Nota anche che se hai POSIX (2008), hai già 'getline'. –

risposta

5

Non utilizzare fread. Utilizzare fgets. Immagino che questo sia un problema per i compiti a casa/classe, quindi non sto fornendo una risposta completa, ma se dici che non lo è, darò ulteriori consigli. È sicuramente possibile fornire il 100% della semantica di GNU-style getline, inclusi i byte null incorporati, usando puramente fgets, ma richiede un pensiero intelligente.

OK, aggiornamento dal momento che non si tratta di compiti a casa:

  • memset buffer a '\n'.
  • Utilizzare fgets.
  • Utilizzare memchr per trovare il primo '\n'.
  • Se non viene trovato '\n', la riga è più lunga del buffer. Ingrandire il buffer, riempire la nuova porzione con '\n' e fgets nella nuova porzione, ripetendo se necessario.
  • Se il carattere successivo a '\n' è '\0', quindi fgets terminato per il raggiungimento della fine di una riga.
  • Altrimenti, terminato per raggiungere EOF, '\n' viene lasciato da memset, il carattere precedente è il valore nullo di terminazione scritto fgets e il carattere precedente è l'ultimo carattere dei dati effettivi letti.

È possibile eliminare il memset e utilizzare strlen al posto di memchr se non si cura di sostenere le linee con i null incorporati (in entrambi i casi, il nulla non terminare la lettura, ma sarà solo una parte della vostra sola lettura in linea).

C'è anche un modo per fare la stessa cosa con fscanf e il "%123[^\n]" identificatore (dove 123 è il tuo limite di buffer), che offre la flessibilità di fermarsi a caratteri non di nuova riga (ALA GNU getdelim).Tuttavia è probabilmente lento a meno che il tuo sistema non abbia un'implementazione molto elegante scanf.

+0

Questo non è compito a casa ... :) Come suggeriresti di usare 'fgets'? Usare un array di caratteri in grado di crescere e riempirlo con chiamate successive a 'fgets' sembra complicato da implementare correttamente. Inoltre, capisco che 'fgets' termina incontrando '\ n' o EOF, ma non '\ 0'. Questo non è un problema per i nostri file, però. –

+1

@R .. Un foro minore: dopo aver usato 'char s [5]; memset (s, '\ n', sizeof s); fgets (s, sizeof s, ...); 'su un file con 3 byte" xyz "porta a" xyz \ 0 \ n "in' s'. Trovare il primo ''\ n'' è OK, ma il controllo del seguente carattere è UB. Suggerisci di aggiungere "Se '\ n' nell'ultimo posto, quindi' fgets' è terminato a causa del raggiungimento dell'ultima riga nel file. " quindi passare a "Se il personaggio che segue ..." – chux

+0

Mi chiedo perché così tante funzioni relative alle stringhe abbiano valori di ritorno relativamente inutili? Il codice che chiama 'strcat' e' fgets' spesso ha bisogno di trovare l'ultimo carattere scritto - qualcosa che il codice per quelle funzioni avrà già conosciuto. Non riesco a pensare a nessuna utilità per il valore di ritorno di quelle funzioni implementate. – supercat

1

Non c'è una grande differenza di prestazioni tra fget e fgetc/setvbuf. Prova:

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 
    ... 
} 
Problemi correlati