C:定位到文件中已知行的最佳方法

8

我有一个文件,我想遍历它,但不处理当前行。我要找的是在文本文件中定位到特定行的最佳方法。例如,将当前行存储到变量中似乎没有用,直到我到达预定的行。

示例:

file.txt

foo
fooo
fo
here

通常,要达到这个目的,我会执行以下操作:

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

但是fgets必须无用地读取三行,并且currentLine必须存储foofooofo

有没有更好的方法来做到这一点,知道here是第4行? 例如文件的go to?


2
对于普通文件,唯一更好的方法是构建和维护自己的行号和 fseek 偏移量索引。(这很简单,但需要一些工作。) - Steve Summit
4个回答

8

由于您不知道每行的长度,所以需要逐行读取并计算前面所有行的长度。

如果您知道每一行的长度,可以使用fseek()函数移动文件指针。


6

你无法直接访问文本文件的某一行(除非所有行在字节上大小相同;而且使用UTF8 everywhere时,Unicode字符可以占用可变数量的字节,从1到6不等;并且在大多数情况下,行的长度各不相同-与下一行不同)。因此,你不能使用fseek(因为你事先不知道文件偏移量)。

然而(至少在Linux系统上),行以\n(换行符)结尾。因此,你可以逐字节读取并计数:

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

你之后不需要存储整个行。因此,你可以通过这种方式到达第45行 (使用 while ((c=fgetc(file)) != EOF) && linecount<45 ...),然后再使用fgets或更好的 getline(3) 在POSIX系统上阅读整个行(参见 this 示例)。请注意,fgetsgetline 的实现可能是建立在fgetc之上,或者至少与其共享某些代码。请记住,<stdio.h>缓冲式 I/O,请参见 setvbuf(3) 和相关函数。
另一种方法是进行两次文件读取。第一次读取会将每行开头的偏移量(使用ftell(3)...)存储在某种高效的数据结构中(如向量、哈希表、树形结构等)。第二次读取将使用该数据结构检索偏移量(即行开头),然后使用fseek(3)(使用该偏移量)。

第三种方式,特定于POSIX的,是通过mmap(2)将文件映射到您的虚拟地址空间中(这对于不太大的文件非常有效,例如小于几个GB)。 仔细处理(您可能需要mmap一个额外的结束页,以确保数据以零字节结尾),然后您就可以使用strchr(3)'\n'

在某些情况下,您可能会考虑逐行解析您的文本文件(使用适当的解析,或-Linux上-getline,或使用flexbison生成您的解析器),并将每行存储在关系数据库(例如PostGreSQLsqlite)中。

顺便提一句,行的概念(以及行末标记)因操作系统而异。在Linux中,行末是一个\n字符。在Windows中,据传行末为\r\n等等...


1
从技术上讲,在Windows上,行尾也以\n字符结束...只是在它之前有一个\r。关键是,在Windows上计算\n也是有效的。 - Govind Parmar
逐个字符迭代是否比逐行迭代有优势? - Badda
1
@Badda:你怎样逐行迭代? - Basile Starynkevitch

5
在 C 语言中,FILE * 是一个 char 流。在可寻址文件中,可以使用文件指针和 fseek() 来寻址这些 char。但除此之外,在文件中没有“特殊字符”,换行符只是另一个普通字符而已。
因此,简而言之,如果您不知道每一行的长度,那么不能直接跳转到文本文件的某一行。
C 语言中的模型对应于典型操作系统提供的文件。如果您深入思考,要想知道单独一行的起始点,您的文件系统就必须存储这些信息。这将意味着要特别处理文本文件。
然而,您可以计算行数,而不是匹配模式,就像这样:
#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;
}

1
如果您不知道每行的长度,那么您需要遍历所有行。但是,如果您知道要停止的行,可以这样做:
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++;
    }
}

至少你可以避免这么多对strstr的调用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接