2010-08-01 64 views
4

我正在尝试读取文本文件中的所有内容。这是我写的代码。从文本文件中读取所有内容 - C

#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; 
} 

我想知道这个函数的相对复杂度是什么?

+2

我很难想象使用它作为文件名会得到你很多的荣誉 – 2010-08-01 06:57:18

+1

ahh ..对不起。我正在测试并忘记删除它。非常抱歉。 – 2010-08-01 07:03:50

+0

我认为一般而言,你应该尝试以固定的块进行工作;因此在这种情况下,您将一次读取PAGE_SIZE个字节(或更少的数据,如果它是最后一个数据块),并在读取每个块时打印每个块。 – wj32 2010-08-01 07:11:49

回答

7

您应该尝试查看功能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); 
+0

谢谢。我查找了'fsize'的文档,但找不到一个。这是一个平台独立功能吗? 'fsize'如何在不读取整个文件的情况下告诉文件大小? – 2010-08-01 07:07:51

+0

刚刚更新了我的答案,用fsize替换:) – 2010-08-01 07:09:22

+0

'fsize'看起来像是Windows特有的。 'stat(2)'是UNIX的等价物。 – Wang 2010-08-01 07:14:47

2

人们经常使用realloc以现有尺寸的两倍来获得摊销时间而不是线性。这使缓冲区不会超过两倍,这通常是可以的,并且您可以在完成后重新分配回正确的大小。

但更好的是stat(2)的文件大小和分配一次(有一些额外的空间,如果文件大小是易失性的)。

此外,为什么你不要fgets(3)而不是逐字符读取字符,或者甚至更好,整个事情(或相关的块,如果它太大的内存)。

2

这可能是比较慢的,当然更复杂:

while((c = getc(fp)) != EOF) { 
    putchar(c); 
} 

它做同样的事情,你的代码。

0

在POSIX系统(例如linux)上,系统调用mmap可以将所有文件映射到内存中。它有一个选项来映射该文件拷贝写在,所以如果你改变缓冲区,你会覆盖你的文件。

这通常会更有效率,因为您尽可能多地离开系统。无需做realloc或类似的。

特别是,如果您只是在阅读并且多个进程同时执行此操作,那么整个系统在内存中只会有一个副本。

+0

我认为你对写入时复制的含义感到困惑。如果文件被映射为copy-on-write(私有),则映射最初只是对磁盘文件的引用,但是对其进行的任何更改都会导致您的进程本地数据的副本。如果它被映射为共享,则您的更改将被写入该文件并被其他进程可见。 – 2010-08-01 08:01:14

+0

@R。对磁盘文件的引用?确定所有'mmap'确实这是它的想法。我的意思是,系统可以容纳你在页面缓存中不改变的所有页面,并在进程之间共享这个缓存。在两种情况下都是如此:(1)只要您将事物映射为只读,或(2)如果您使用写时复制并且不更改内容。所以一般来说,如果你认为你需要随机访问文件的全部内容,那么'mmap'几乎总是更好的策略。 'fread'和变体应该仅限于您在给定时间只需要部分访问文件的情况。 – 2010-08-01 12:07:31

1

这是从快速阅读,所以我可能错过了一些问题。

首先,a = realloc(a, ...);是错误的。如果realloc()失败,则返回NULL,但不释放原始内存。由于您重新分配给a,原始内存会丢失(即,它是内存泄漏)。要做到这一点的正确方法是:tmp = realloc(a, ...); if (tmp) a = tmp;

其次,关于使用fseek(fp, 0, SEEK_END);确定文件大小,请注意,这可能会或可能不起作用。如果该文件不是随机访问的(例如stdin),您将无法返回到开始读取它。另外,fseek()后跟ftell()可能无法为二进制文件提供有意义的结果。对于文本文件,它可能不会为您提供可读取的正确字符数。有关此主题的一些有用信息comp.lang.c常问问题question 19.2

此外,在您的原始代码中,当它等于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.

希望帮助隐式转换为任何其他对象的指针类型。

+0

如果它指向一个可搜索的文件,'stdin'是可搜索的。如果它是一个交互设备,管道等,那么它是不可搜索的。在任何合理的系统上,'fseek' /'ftell' **对于二进制文件**是可靠的。是的C标准祖先 - 在旧版本的实现中,二进制文件可以有随机尾随零字节,但这是2010年,所有真实的当今系统都有真正的二进制文件。由于不可预测的错误行为,不应使用文本模式。只要自己去除'\ r'。 – 2010-08-01 08:04:55

+0

@R ..:在我的Mac上,'fseek(stdin,0,SEEK_END)'成功了,'ftell()'返回0,然后我就可以根据需要从'stdin'中读取尽可能多的字符。在linux上,'fseek(stdin,0,SEEK_END);'导致'非法查找'(同一个程序)。我更喜欢基于'realloc()'的方法,因为那样我就不必处理像自己剥离'\ r'这样的事情了,它也适用于不可查找的文件。 – 2010-08-01 08:21:26

+0

除非你有理由需要整个文件在内存中,否则你应该遵循msw的答案,它没有失败的情况和易于证明的正确性。顺便说一句,如果你想剥离'\ r'(例如从Windows文本文件),你必须自己动手。只有Windows和旧版Mac(pre-OSX)具有“文本模式”文件操作,这些文件操作会破坏数据。 POSIX要求文本模式的行为与二进制模式相同,并且在OSX,Linux等上使用。 – 2010-08-01 08:53:03

1

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