从文本文件中读取所有内容 - 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。但是这个代码预计是跨平台的。

任何帮助将不胜感激。

编辑

这是带有freadftell 的更新代码。

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) &gt; 0) 都更简单:-)。有关 POSIX 要求的有趣信息。我不知道 - 谢谢!【参考方案6】:

我在这里看到的一个问题是变量index,它是非递减的。所以条件 if(!fcontent || index == PAGE_SIZE) 只会出现一次。所以我认为检查应该像 index%PAGE_SIZE == 0 而不是 index == PAGE_SIZE

【讨论】:

以上是关于从文本文件中读取所有内容 - C的主要内容,如果未能解决你的问题,请参考以下文章

如何用VBS逐行读取文本文件的内容,并输入变量

在 C 中,我应该如何读取文本文件并打印所有字符串

如何在 C 编程中使用 fgets 编写返回文本文件所有内容的函数?

易语言读取文件

关于C语言中文本文件的逐行读取的实现

vba adodb读取文本文件