从文本文件中读取所有内容 - C
Posted
技术标签:
【中文标题】从文本文件中读取所有内容 - C【英文标题】:Reading all content from a text file - C 【发布时间】:2011-03-23 19:18:36 【问题描述】:我正在尝试从文本文件中读取所有内容。这是我写的代码。
#include <stdio.h>
#include <stdlib.h>
#define PAGE_SIZE 1024
static char *readcontent(const char *filename)
char *fcontent = NULL, c;
int index = 0, pagenum = 1;
FILE *fp;
fp = fopen(filename, "r");
if(fp)
while((c = getc(fp)) != EOF)
if(!fcontent || index == PAGE_SIZE)
fcontent = (char*) realloc(fcontent, PAGE_SIZE * pagenum + 1);
++pagenum;
fcontent[index++] = c;
fcontent[index] = '\0';
fclose(fp);
return fcontent;
static void freecontent(char *content)
if(content)
free(content);
content = NULL;
这是用法
int main(int argc, char **argv)
char *content;
content = readcontent("filename.txt");
printf("File content : %s\n", content);
fflush(stdout);
freecontent(content);
return 0;
由于我是 C 新手,我想知道这段代码看起来是否完美?您是否看到任何问题/改进?
使用的编译器:GCC。但是这个代码预计是跨平台的。
任何帮助将不胜感激。
编辑
这是带有fread
和ftell
的更新代码。
static char *readcontent(const char *filename)
char *fcontent = NULL;
int fsize = 0;
FILE *fp;
fp = fopen(filename, "r");
if(fp)
fseek(fp, 0, SEEK_END);
fsize = ftell(fp);
rewind(fp);
fcontent = (char*) malloc(sizeof(char) * fsize);
fread(fcontent, 1, fsize, fp);
fclose(fp);
return fcontent;
我想知道这个函数的相对复杂性是多少?
【问题讨论】:
我几乎不认为使用它作为文件名会给你带来很多荣誉 啊……对不起。我正在测试并忘记删除它。非常抱歉。 我认为一般来说你应该尝试在固定的块中工作;因此在这种情况下,您将一次读取 PAGE_SIZE 个字节(如果是最后一个块,则读取更少)并在读取每个块时打印它们。 如果您没有“filename.txt”的读取权限,您知道您的代码会做什么吗?这是你想要它做的吗? 您应该将c
设为int
。 ([f|]getc()
返回整数)
【参考方案1】:
您应该尝试查看函数 fsize
(关于 fsize,请参阅下面的更新)和 fread
。这可能会带来巨大的性能提升。
使用fsize
获取您正在阅读的文件的大小。仅使用此大小进行一次内存分配。 (关于 fsize,见下文更新。获取文件大小并做一次 alloc 的思路还是一样的)。
使用fread
对文件进行块读取。这比单字符读取文件要快得多。
类似这样的:
long size = fsize(fp);
fcontent = malloc(size);
fread(fcontent, 1, size, fp);
更新
不确定 fsize 是否跨平台,但您可以使用此方法获取文件的大小:
fseek(fp, 0, SEEK_END);
size = ftell(fp);
fseek(fp, 0, SEEK_SET);
【讨论】:
谢谢。我查找了fsize
的文档,但找不到。这是独立于平台的功能吗? fsize
如何在不读取整个文件的情况下判断文件大小?
fsize
看起来像是特定于 Windows 的。 stat(2)
是 UNIX 等价物。
请勿将stat
用于此目的。如果“文件”不是普通文件而是其他文件(可能是硬盘分区),您将无法获得大小。始终使用搜索到结束的方法来确定大小。如果您打算支持从不可搜索的源(如管道或套接字)读取,那么如果 ftell
返回 -1,您可能还应该支持增量 realloc 方法。
@R.当然在这种情况下应该使用stat
。该问题明确指出这是关于文本文件的。【参考方案2】:
人们经常将realloc
增加到现有大小的两倍以获得摊销的常数时间而不是线性时间。这使得缓冲区不超过两倍大,这通常是可以的,并且您可以选择在完成后重新分配回正确的大小。
但更好的是stat(2)
为文件大小分配一次(如果文件大小不稳定,则有一些额外的空间)。
另外,为什么你不fgets(3)
而不是逐个字符地阅读,或者更好的是mmap(2)
整个事情(或者如果它对于内存来说太大,则相关块)。
【讨论】:
【参考方案3】:它可能比:
while((c = getc(fp)) != EOF)
putchar(c);
它和你的代码做同样的事情。
【讨论】:
【参考方案4】:在 POSIX 系统(例如 linux)上,您可以通过将所有文件映射到内存中的系统调用 mmap
获得相同的效果。它具有映射该文件的选项写入时复制,因此如果您更改缓冲区,您将覆盖您的文件。
这通常会更有效率,因为您尽可能多地留给系统。无需执行realloc
或类似操作。
特别是,如果您只是读取并且多个进程同时读取,则整个系统的内存中将只有一个副本。
【讨论】:
我认为您对写时复制的含义感到困惑。如果文件被映射为写时复制(私有),则该映射最初只是对磁盘文件的引用,但您对其所做的任何更改都将导致您的进程本地数据的副本。如果它是映射共享的,那么您的更改将被写入文件并被其他进程看到。 @R.对磁盘文件的引用?确定所有mmap
都这样做了,这就是它的想法。我的意思是系统可以在其页面缓存中保存所有您不更改的页面,并在进程之间共享此缓存。这适用于两种情况:(1)只要您以只读方式映射事物或(2)如果您使用写时复制并且不更改内容。因此,一般来说,如果您认为需要随机访问文件的全部内容,mmap
几乎总是更好的策略。 fread
和变体应仅限于在给定时间只需要部分访问文件的情况。【参考方案5】:
这是快速阅读,所以我可能遗漏了一些问题。
首先,a = realloc(a, ...);
是错误的。如果realloc()
失败,它会返回NULL
,但不会释放原始内存。由于您重新分配给a
,原始内存丢失(即,它是内存泄漏)。正确的做法是:tmp = realloc(a, ...); if (tmp) a = tmp;
等
其次,关于使用fseek(fp, 0, SEEK_END);
确定文件大小,请注意这可能有效,也可能无效。如果文件不是随机访问的(例如stdin
),您将无法回到开头阅读它。此外,fseek()
后跟 ftell()
可能不会为二进制文件提供有意义的结果。对于文本文件,它可能无法为您提供正确数量的可读取字符。在comp.lang.c
FAQ question 19.2 上有一些关于这个主题的有用信息。
另外,在您的原始代码中,当index
等于PAGESIZE
时,您不会将index
设置为0,因此如果您的文件长度大于2*PAGESIZE
,您将覆盖缓冲区。
您的freecontent()
函数:
static void freecontent(char *content)
if(content)
free(content);
content = NULL;
没用。它只将content
的副本设置为NULL
。就像你写了这样一个函数setzero
:
void setzero(int i) i = 0;
一个更好的主意是自己跟踪内存,而不是释放任何比需要更多或更少的东西。
您不应在 C 中强制转换 malloc()
或 realloc()
的返回值,因为 void *
在 C 中隐式转换为任何其他对象指针类型。
希望对您有所帮助。
【讨论】:
stdin
是可搜索的,如果它指的是可搜索的文件。如果它是交互式设备、管道等,则它是不可搜索的。fseek
/ftell
is 在任何合理系统上的二进制文件上都是可靠的。是的,C 标准的祖辈在遗留实现中二进制文件可以有随机尾随零字节,但这是 2010 年,所有真正的当今系统都有真正的二进制文件。由于不可预测和错误的行为,根本不应该使用文本模式。只需自己剥离\r
。
@R..:在我的 Mac 上,fseek(stdin, 0, SEEK_END)
成功,ftell()
返回 0,然后我可以从 stdin
读取任意数量的字符。在 linux 上,fseek(stdin, 0, SEEK_END);
的结果是 Illegal seek
(同一个程序)。我更喜欢基于realloc()
的方法,因为这样我就不必自己处理诸如剥离\r
之类的事情,而且它也适用于不可搜索的文件。
除非有理由需要将整个文件保存在内存中,否则您可能应该遵循 msw 的答案,该答案没有失败案例并且易于证明正确性。顺便说一句,如果您想删除 \r
(例如从 Windows 文本文件中),无论如何您都必须自己动手。只有 Windows 和旧版 Mac(OSX 之前)具有破坏数据的“文本模式”文件操作。 POSIX 要求文本模式的行为与二进制模式相同,在 OSX、Linux 等上也是如此。
@Alok:谢谢。你有一个非常有效的观点。我了解使用 ftell() 和 fseek() 来查找文件大小不是正确的方法。 securecoding.cert.org/confluence/display/seccode/… 解释了这一点。那么你是说我应该使用我首先拥有的代码和你建议的更改吗?
@R.. 当然,如果整个目标是打印文件,则不需要复杂的代码。 while ((c = getchar()) != EOF)
或 while ((nread = fread(buf, 1, sizeof buf, fp) > 0)
都更简单:-)。有关 POSIX 要求的有趣信息。我不知道 - 谢谢!【参考方案6】:
我在这里看到的一个问题是变量index
,它是非递减的。所以条件
if(!fcontent || index == PAGE_SIZE)
只会出现一次。所以我认为检查应该像
index%PAGE_SIZE == 0
而不是 index == PAGE_SIZE
。
【讨论】:
以上是关于从文本文件中读取所有内容 - C的主要内容,如果未能解决你的问题,请参考以下文章