【发布时间】:2010-12-20 11:51:52
【问题描述】:
使用 C 我想以这样一种方式读取文本文件的内容,以便在说完所有内容后获得一个字符串数组,其中第 n 个字符串表示文本文件的第 n 行。文件的行数可以任意长。
完成此任务的优雅方式是什么?我知道将文本文件直接读取到单个大小合适的缓冲区中的一些巧妙技巧,但是将其分成几行会使它变得更加棘手(至少据我所知)。
非常感谢!
【问题讨论】:
使用 C 我想以这样一种方式读取文本文件的内容,以便在说完所有内容后获得一个字符串数组,其中第 n 个字符串表示文本文件的第 n 行。文件的行数可以任意长。
完成此任务的优雅方式是什么?我知道将文本文件直接读取到单个大小合适的缓冲区中的一些巧妙技巧,但是将其分成几行会使它变得更加棘手(至少据我所知)。
非常感谢!
【问题讨论】:
你可以这样用
#include <stdlib.h> /* exit, malloc, realloc, free */
#include <stdio.h> /* fopen, fgetc, fputs, fwrite */
struct line_reader {
/* All members are private. */
FILE *f;
char *buf;
size_t siz;
};
/*
* Initializes a line reader _lr_ for the stream _f_.
*/
void
lr_init(struct line_reader *lr, FILE *f)
{
lr->f = f;
lr->buf = NULL;
lr->siz = 0;
}
/*
* Reads the next line. If successful, returns a pointer to the line,
* and sets *len to the number of characters, at least 1. The result is
* _not_ a C string; it has no terminating '\0'. The returned pointer
* remains valid until the next call to next_line() or lr_free() with
* the same _lr_.
*
* next_line() returns NULL at end of file, or if there is an error (on
* the stream, or with memory allocation).
*/
char *
next_line(struct line_reader *lr, size_t *len)
{
size_t newsiz;
int c;
char *newbuf;
*len = 0; /* Start with empty line. */
for (;;) {
c = fgetc(lr->f); /* Read next character. */
if (ferror(lr->f))
return NULL;
if (c == EOF) {
/*
* End of file is also end of last line,
` * unless this last line would be empty.
*/
if (*len == 0)
return NULL;
else
return lr->buf;
} else {
/* Append c to the buffer. */
if (*len == lr->siz) {
/* Need a bigger buffer! */
newsiz = lr->siz + 4096;
newbuf = realloc(lr->buf, newsiz);
if (newbuf == NULL)
return NULL;
lr->buf = newbuf;
lr->siz = newsiz;
}
lr->buf[(*len)++] = c;
/* '\n' is end of line. */
if (c == '\n')
return lr->buf;
}
}
}
/*
* Frees internal memory used by _lr_.
*/
void
lr_free(struct line_reader *lr)
{
free(lr->buf);
lr->buf = NULL;
lr->siz = 0;
}
/*
* Read a file line by line.
* http://rosettacode.org/wiki/Read_a_file_line_by_line
*/
int
main()
{
struct line_reader lr;
FILE *f;
size_t len;
char *line;
f = fopen("foobar.txt", "r");
if (f == NULL) {
perror("foobar.txt");
exit(1);
}
/*
* This loop reads each line.
* Remember that line is not a C string.
* There is no terminating '\0'.
*/
lr_init(&lr, f);
while (line = next_line(&lr, &len)) {
/*
* Do something with line.
*/
fputs("LINE: ", stdout);
fwrite(line, len, 1, stdout);
}
if (!feof(f)) {
perror("next_line");
exit(1);
}
lr_free(&lr);
return 0;
}
【讨论】:
可以读取文件中的行数(循环 fgets),然后创建一个二维数组,第一维是行数+1。然后,只需将文件重新读入数组即可。
不过,您需要定义元素的长度。或者,计算最长的行大小。
示例代码:
inFile = fopen(FILENAME, "r");
lineCount = 0;
while(inputError != EOF) {
inputError = fscanf(inFile, "%s\n", word);
lineCount++;
}
fclose(inFile);
// Above iterates lineCount++ after the EOF to allow for an array
// that matches the line numbers
char names[lineCount][MAX_LINE];
fopen(FILENAME, "r");
for(i = 1; i < lineCount; i++)
fscanf(inFile, "%s", names[i]);
fclose(inFile);
【讨论】:
将其分解为行意味着解析文本并将所有 EOL(EOL 我的意思是 \n 和 \r)字符替换为 0。 通过这种方式,您实际上可以重用缓冲区并将每行的开头存储到单独的 char * 数组中(只需执行 2 遍)。
通过这种方式,您可以对整个文件大小进行一次读取+2 次解析,这可能会提高性能。
【讨论】:
realloc() 和strtok() 进行单次传递。
malloc() 为阵列分配您认为需要的空间。从缓冲区开始。对于每个 '\n',替换为 0,并将下一个字符的地址放入数组中。跟踪数组大小;如果它会溢出,realloc() 它。
如果您有一种将整个文件读入内存的好方法,那么您就快到了。完成后,您可以扫描文件两次。一次计算行数,一次设置行指针并将“\n”和(如果文件以 Windows 二进制模式读取,则可能是“\r”)替换为“\0”。在扫描之间分配一个指针数组,现在您知道需要多少。
【讨论】:
也许链接列表是最好的方法? 编译器不喜欢有一个不知道有多大的数组。使用链接列表,您可以拥有一个非常大的文本文件,而不必担心为数组分配足够的内存。
很遗憾,我还没有学会如何做链表,但也许其他人可以帮助你。
【讨论】:
对于 C(相对于 C++),您可能最终会使用 fgets()。但是,您可能会因为任意长度的线条而遇到问题。
【讨论】: