【问题标题】:read file line by line including multiple newline characters逐行读取文件,包括多个换行符
【发布时间】:2016-10-08 16:11:32
【问题描述】:

我正在尝试逐行读取大小未知的文件,包括单个或多个换行符。 例如,如果我的 sample.txt 文件看起来像这样

abc   cd er  dj
text

more   text


zxc cnvx

我希望我的字符串看起来像这样

string1 = "abc   cd er  dj\n";
string2 = "text\n\n";
string3 = "more   text\n\n\n";
string4 = "zxc convex";

我似乎想不出可以正常工作的解决方案。我尝试了以下代码来获取包括换行符在内的每一行的长度,但它给了我不正确的长度

while((temp = fgetc(input)) != EOF) {
    if (temp != '\n') {
        length++;
    }
    else {
        if (temp == '\n') {
            while ((temp = fgetc(input)) == '\n') {
                length++;
            }
        }
        length = 0;
    } 
}

我在想,如果我可以获取包括换行符在内的每一行的长度,然后我可以 malloc 该长度的字符串,然后使用 fread 读取该大小的字符串,但我不确定这是否可行,因为我将不得不移动文件指针以获取下一个字符串。

我也不想使用缓冲区,因为我不知道每行的长度。任何形式的帮助将不胜感激。

【问题讨论】:

  • 只获取每行的长度。如果我不这样做,那么它将继续增加长度。
  • 为字符串分配内存。使用fgets 读取文件。如果字符串末尾不存在newline(并且您不在文件末尾),则不会读取整行,因此您重新分配更长的字符串并继续读取到末尾以前的数据。
  • 行没有平均大小。有些行可能太大,有些可能太小。我也不知道文件会有多大。它可以包含几行或 16,000 行的书。
  • @WeatherVane 我应该如何为字符串分配内存?我不知道每行的大小。我应该只分配大块内存,然后根据需要重新分配吗?
  • 分配任意数量。当它太小时,以两倍大小或固定增量重新分配。如果需要,您甚至可以每行多次这样做。

标签: c string file


【解决方案1】:

如果行很短并且数量不多,您可以根据需要使用realloc 重新分配内存。或者您可以使用更小(或更大)的块并重新分配。这有点浪费,但希望它最终会平均化。

如果您只想使用一个分配,则找到下一个非空行的开头并保存文件位置(使用ftell)。然后得到当前位置和上一个起始位置之间的差异,你就知道要分配多少内存了。对于阅读,是的,您必须来回寻找,但如果不是很大,所有数据都将在缓冲区中,它只是在修改一些指针。读完后寻找保存的位置,使其成为下一个起始位置。

那么你当然可以memory-map the file。这会将文件内容放入您的内存映射中,就像它已全部分配一样。对于 64 位系统,地址空间足够大,因此您应该能够映射数 GB 的文件。然后你不需要寻找或分配内存,你所做的只是操纵指针而不是寻找。读取只是一个简单的内存复制(但由于文件已经“在”内存中,您并不真的需要它,只需保存指针即可)。


对于fseekftell 上的一个非常 简单示例,这与您的问题有些相关,我为您整理了这个小程序。它实际上并没有做任何特别的事情,但它展示了如何以可以用于我上面讨论的第二种方法的原型的方式使用这些函数。

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    FILE *file = fopen("some_text_file.txt", "r");

    // The position after a successful open call is always zero
    long start_of_line = 0;

    int ch;

    // Read characters until we reach the end of the file or there is an error
    while ((ch = fgetc(file)) != EOF)
    {
        // Hit the *first* newline (which differs from your problem)
        if (ch == '\n')
        {
            // Found the first newline, get the current position
            // Note that the current position is the position *after* the newly read newline
            long current_position = ftell(file);

            // Allocate enough memory for the whole line, including newline
            size_t bytes_in_line = current_position - start_of_line;
            char *current_line = malloc(bytes_in_line + 1);  // +1 for the string terminator

            // Now seek back to the start of the line
            fseek(file, start_of_line, SEEK_SET);  // SEEK_SET means the offset is from the beginning of the file

            // And read the line into the buffer we just allocated
            fread(current_line, 1, bytes_in_line, file);

            // Terminate the string
            current_line[bytes_in_line] = '\0';

            // At this point, if everything went well, the file position is
            // back at current_position, because the fread call advanced the position
            // This position is the start of the next line, so we use it
            start_of_line = current_position;

            // Then do something with the line...
            printf("Read a line: %s", current_line);

            // Finally free the memory we allocated
            free(current_line);
        }

        // Continue loop reading character, to read the next line
    }

    // Did we hit end of the file, or an error?
    if (feof(file))
    {
        // End of the file it is

        // Now here's the tricky bit. Because files doesn't have to terminated
        // with a newline, at this point we could actually have some data we
        // haven't read. That means we have to do the whole thing above with
        // the allocation, seeking and reading *again*

        // This is a good reason to extract that code into its own function so
        // you don't have to repeat it

        // I will not repeat the code my self. Creating a function containing it
        // and calling it is left as an exercise
    }

    fclose(file);

    return 0;
}

请注意,为简洁起见,该程序不包含任何错误处理。还应该注意的是,我实际上并没有尝试过该程序,甚至没有尝试编译它。都是专门针对这个答案写的。

【讨论】:

  • @samadbond 这取决于。您是否需要一次将所有行保存在内存中?您应该如何处理读取的数据?哦,我已经编辑了答案以包含第三种方式,它不需要您进行任何搜索甚至(主动)内存分配。
  • 我会将这些字符串添加到我的链接列表中。我正在尝试将纯文本转换为 html 文本,我尝试阅读换行符的原因是因为 html 有段落标签
  • 内存映射对我来说似乎有点复杂。我认为有一个简单的解决方案可以读取包括换行符在内的每一行。
  • 我以前也从未使用过 fseek,所以我不知道如何使 fseek 指针指向下一个字符串的开头
  • @samadbond 添加了一个简单示例,说明如何使用 fseekftell 可能对您有用。
【解决方案2】:

除非您尝试编写自己的实现,否则您可以使用the standard POSIX getline() function

#include <stdio.h>
#include <stdlib.h>


int main(void)
{
    FILE *fp;
    char *line = NULL;
    size_t len = 0;
    ssize_t read;
    fp = fopen("/etc/motd", "r");
    if (fp == NULL)
        exit(1);
    while ((read = getline(&line, &len, fp)) != -1) {
        printf("Retrieved line of length %zu :\n", read);
        printf("%s", line);
    }
    if (ferror(fp)) {
        /* handle error */
    }
    free(line);
    fclose(fp);
    return 0;
}

【讨论】:

  • 感谢您的回复,但您的解决方案并不能解决我的问题。我也在尝试阅读换行符。
  • @samadbond 感谢您的回复,但您的解决方案并不能解决我的问题。我也在尝试阅读换行符。 是什么让您认为 getline() 不阅读换行符?您阅读getline() 的文档了吗?它说“成功完成后,getline() 和 getdelim() 函数应返回写入缓冲区的字节数,如果在 EOF 之前遇到分隔符,则包括分隔符。”
  • 它只包含一个换行符。当我使用我的 sample.txt 文件运行您的代码时,它打印的“文本”长度为 5,而不是 6
  • @samadbond 因此,如果一行仅包含一个 \n 换行符,请将其添加到上一行。像if (!strcmp(line,"\n")){ strcat(previous_line, line); } else { previous_line = line; previous_len = len } 这样的东西会比这更复杂一些,但即使跟踪前一行,这也比重新实现和调试getline() 要容易得多。重新实现和调试库函数是困难。见glibc getdelim() internals
【解决方案3】:

你得到了错误的长度。原因是在进入循环之前:

while ((temp = fgetc(input)) == '\n')

您忘记增加length,因为它刚刚读取了\n 字符。所以这些行必须变成:

else {
    length++;                // add the \n just read
    if (temp == '\n') {      // this is a redundant check
        while ((temp = fgetc(input)) == '\n') {
            length++;
        }
        ungetc(temp, input);
    }


编辑

在阅读了第一个非\n之后,你现在已经阅读了下一行的第一个字符,所以你必须取消它:

ungetc(temp, input);

【讨论】:

  • 它只固定第一个字符串的长度。其他字符串的长度仍然不正确。
  • 希望我的最后一次编辑修复了它。不幸的是,我目前无法对其进行测试。
  • 那么请点击向上箭头批准我的回答。祝你好运。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-01-25
相关资源
最近更新 更多