从文件中读取一行,而不知道该行的长度。

29

我想逐行读取一个文件,但不知道每行的长度。以下是我目前的代码:

int ch = getc(file);
int length = 0;
char buffer[4095];

while (ch != '\n' && ch != EOF) {
    ch = getc(file);
    buffer[length] = ch;
    length++;
}

printf("Line length: %d characters.", length);

char newbuffer[length + 1];

for (int i = 0; i < length; i++)
    newbuffer[i] = buffer[i];

newbuffer[length] = '\0';    // newbuffer now contains the line.

我现在能够确定行长,但只限于长度小于4095个字符的行,此外,两个字符数组似乎是完成任务的一种笨拙方式。 有更好的方法吗?(我已经尝试使用fgets()函数,但被告知这不是最佳方法)。

--Ry

5个回答

18

你可以从一开始就选择适当的大小,然后在需要更多空间时使用realloc函数:

int CUR_MAX = 4095;
char *buffer = (char*) malloc(sizeof(char) * CUR_MAX); // allocate buffer.
int length = 0;

while ( (ch != '\n') && (ch != EOF) ) {
    if(length ==CUR_MAX) { // time to expand ?
      CUR_MAX *= 2; // expand to double the current size of anything similar.
      buffer = realloc(buffer, CUR_MAX); // re allocate memory.
    }
    ch = getc(file); // read from stream.
    buffer[length] = ch; // stuff in buffer.
    length++;
}
.
.
free(buffer);

在调用mallocrealloc之后,您需要检查是否有分配错误。


只是提醒一下,逐字逐句的阅读速度非常慢。你应该以大块(4-16k)为单位进行阅读。 - Blindy
7
标准库的I/O会进行缓冲,所以这种读取方式(通过一次性读取大块数据)并不比逐块读取慢。(much可省略) - JaakkoK
2
重置计数器为0会导致缓冲区溢出吗? - fbstj
2
请遵循惯例,不要将malloc()的结果强制转换 - Quentin
为什么在扩大内存大小后将计数器重置为0?之前的内存还在吗? - Roy Li
@Blindy,使用stdio包的getc(3)fgetc(3)逐个字符读取是没有问题的,因为stdio会进行完全缓冲。请检查一下,因为你错了。顺便说一句,自己选择一个不好的缓冲区大小(与stdio包的方式相反)可能会导致更糟糕的资源分配,并影响整个程序的效率。 - Luis Colorado

6

不错!我相信大多数类UNIX系统都已经安装了glibc,所以这绝对是读取行的好方法。 - ryyst
此外,getline 已经被包含在最新的 POSIX 标准中,所以它现在是 Unix 标准。但仍不能保证它是纯 C 的标准库函数。 - dmckee --- ex-moderator kitten

1

这就是我为标准输入流所做的,如果您像这样调用它:readLine(NULL, 0),该函数将为您分配一个大小为1024的缓冲区,并让它以1024的步长增长。如果您使用readLine(NULL, 10)进行调用,则会获得一个以10为步长的缓冲区。如果您有一个缓冲区,则可以提供其大小。

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

char *readLine(char **line, size_t *length)
{
    assert(line != NULL);
    assert(length != NULL);

    size_t count = 0;

    *length = *length > 0 ? *length : 1024;

    if (!*line)
    {
        *line = calloc(*length, sizeof(**line));
        if (!*line)
        {
            return NULL;
        }
    }
    else
    {
        memset(*line, 0, *length);
    }

    for (int ch = getc(stdin); ch != '\n' && ch != EOF; ch = getc(stdin))
    {
        if (count == *length)
        {
            *length += 2;
            *line = realloc(*line, *length);
            if (!*line)
            {
                return NULL;
            }
        }

        (*line)[count] = (char)ch;

        ++count;
    }

    return *line;
}

1

你已经接近成功了。基本上,你需要读取数据块并检查其中是否包含\n字符。如果找到了,那么你就找到了一行的结尾。如果没有找到,你需要增加缓冲区的大小(即分配一个新的缓冲区,大小是原来的两倍,并将数据从旧缓冲区复制到新缓冲区,然后删除旧缓冲区并将新缓冲区重命名为旧缓冲区 -- 或者如果你在使用C语言,可以直接使用realloc函数),然后继续读取数据,直到找到结尾。

一旦你找到了结尾,从缓冲区开头到\n字符的文本就是你的一行。将其复制到另一个缓冲区或直接在原地处理都可以,取决于你的需求。

当你准备好处理下一行时,你可以将输入的“剩余”部分复制到当前行之后(基本上是左移),然后用输入中的数据填充缓冲区的其余部分。然后再次执行上述步骤,直到没有更多的数据可读取。

当然,这个过程可以进行优化,例如使用循环缓冲区,但对于任何合理的io-bound算法来说,这应该已经足够了。


1
考虑使用 scanf 的 '%m' 格式转换修饰符(POSIX)。
char *arr = NULL ;
    // Read unlimited string, terminated with newline. Similar to dynamic size fgets.
if ( fscanf(stdin, "%m[^\n]", &arr) == 1 ) {
   // Do something with arr
   free(arr) ;
} ;

引用自scanf手册:

一个可选的'm'字符。 这与字符串转换 (%s, %c, %[) 一起使用,并减轻了调用者分配相应缓冲区以容纳输入的需要: 相反,scanf() 分配足够大小的缓冲区,并将该缓冲区的地址分配给相应的指针参数,该参数应该是一个 char * 变量的指针 (在调用之前不需要初始化此变量)。调用者应该在不再需要时释放此缓冲区


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