2017-05-29 4 views
8

J'ai un fichier dans lequel je voudrais parcourir sans traiter la ligne en cours. Ce que je cherche est la meilleure façon d'aller à une ligne déterminée d'un fichier texte. Par exemple, stocker la ligne courante dans une variable semble inutile jusqu'à ce que j'arrive à la ligne prédéterminée.C: Meilleure façon d'accéder à une ligne connue d'un fichier

Exemple:

file.txt

foo 
fooo 
fo 
here 

Normalement, afin d'obtenir here, je l'aurais fait quelque chose comme:

FILE* file = fopen("file.txt", "r"); 
if (file == NULL) 
    perror("Error when opening file "); 
char currentLine[100]; 
while(fgets(currentLine, 100, file)) 
{ 
    if(strstr(currentLine, "here") != NULL) 
     return currentLine; 
} 

Mais fgets devra lire entièrement trois ligne inutilement et currentLine devra stocker foo, fooo et fo.

Y a-t-il un meilleur moyen de le faire, sachant que here est la ligne 4? Quelque chose comme un go to mais pour les fichiers?

+2

Pour les fichiers ordinaires, la seule façon de faire mieux est de construire et de maintenir votre propre indice de numéros de ligne et décalages 'fseek'. (Ceci est simple, mais un peu de travail.) –

Répondre

5

Vous ne pouvez pas accéder directement à une ligne donnée d'un fichier texte (à moins que un caractère Unicode peut prendre un nombre variable d'octets, de 1 à 6, et dans la plupart des cas, les lignes ont des longueurs différentes (différentes d'une ligne à l'autre). Donc, vous ne pouvez pas utiliser fseek (parce que vous ne savez pas à l'avance le décalage de fichier).

Cependant (au moins sur les systèmes Linux), les lignes se terminent par \n (le caractère de retour à la ligne). Ainsi, vous pouvez lire octet par octet et les compter:

int c= EOF; 
int linecount=1; 
while ((c=fgetc(file)) != EOF) { 
    if (c=='\n') 
    linecount++; 
} 

Vous n'avez alors pas besoin de stocker la ligne entière.

vous pouvez donc atteindre la ligne n ° 45 de cette façon (en utilisant while ((c=fgetc(file)) != EOF) && linecount<45) ...) et seulement lire ensuite des lignes entières avec fgets ou mieux encore getline(3) sur les systèmes POSIX (voir this exemple). Notez que l'implémentation de fgets ou de getline est susceptible d'être construite au-dessus de fgetc, ou du moins de partager du code avec elle. Rappelez-vous que <stdio.h> est tamponné E/S, voir setvbuf(3) et les fonctions connexes.


Une autre façon serait de lire le fichier en deux passes. Un premier passage enregistre le décalage (en utilisant ftell(3) ...) de chaque début de ligne dans une structure de données efficace (un vecteur, une table de hachage, un arbre ...). Un second passage utilise cette structure de données pour récupérer le décalage (du début de ligne), puis utilise fseek(3) (en utilisant ce décalage).


Une troisième voie, spécifique Posix, serait de mémoire la carte en utilisant le fichier mmap(2) dans votre virtual address space (cela fonctionne bien pour les fichiers pas trop énormes, par exemple de moins de quelques giga-octets). Avec des soins (vous pourriez avoir besoin de mmap une page de fin supplémentaire, pour assurer que les données est mis fin à zéro octet), vous serez alors en mesure d'utiliser strchr(3) avec '\n'

PS. BTW, la notion de lignes (et la marque de fin de ligne) varient d'un OS à l'autre. Sous Linux, la fin de ligne est un caractère \n. Sur les lignes Windows sont selon la rumeur pour mettre fin à \r\n, etc ...

+1

Techniquement sous Windows, les lignes se terminent par le caractère '\ n' ... elles ont juste un' \ r' avant. Le fait est que, compter '\ n's fonctionnera aussi sur Windows. –

+0

Y at-il un avantage à itérer caractère par caractère au lieu de ligne par ligne? – Badda

+1

@Badda: comment iriez-vous ligne par ligne? –

8

Puisque vous ne connaissez pas la longueur de chaque ligne, pas, vous devrez passer par les lignes précédentes.

Si vous connaissiez la longueur de chaque ligne, vous pourriez probablement jouer avec combien d'octets pour déplacer le pointeur de fichier. Vous pouvez le faire avec fseek().

1

Si vous ne connaissez pas la longueur de chaque ligne, vous devez parcourir toutes les lignes. Mais si vous connaissez la ligne que vous voulez vous arrêter pouvez le faire:

while (!found && fgets(line, sizeof line, file) != NULL) /* read a line */ 
{ 
    if (count == lineNumber) 
    { 
     //you arrived at the line 
     //in case of a return first close the file with "fclose(file);" 
     found = true; 
    } 
    else 
    { 
     count++; 
    } 
} 

Au moins, vous pouvez éviter tant d'appels à strstr

5

A FILE * en C est un flux de char s. Dans un fichier de recherche, vous pouvez adresser ces char en utilisant le pointeur de fichier avec fseek(). Mais en dehors de cela, il n'y a pas de "caractères spéciaux" dans les fichiers, une nouvelle ligne est juste un autre caractère normal. Donc, en bref, non, vous ne pouvez pas passer directement à une ligne d'un fichier texte, tant que vous ne connaissez pas la longueur des lignes à l'avance.

Ce modèle en C correspond aux fichiers fournis par les systèmes d'exploitation typiques. Si vous y réfléchissez, pour connaître les points de départ de chaque ligne, votre système de fichiers devrait stocker cette information quelque part. Cela signifierait traiter les fichiers texte spécialement.

Ce que vous pouvez ne compte est cependant que les lignes au lieu de correspondance de motif, quelque chose comme ceci:

#include <stdio.h> 

int main(void) 
{ 
    char linebuf[1024]; 
    FILE *input = fopen("seekline.c", "r"); 
    int lineno = 0; 
    char *line; 
    while (line = fgets(linebuf, 1024, input)) 
    { 
     ++lineno; 
     if (lineno == 4) 
     { 
      fputs("4: ", stdout); 
      fputs(line, stdout); 
      break; 
     } 
    } 
    fclose(input); 
    return 0; 
}