【问题标题】:Fastest way to get char* from file in C从 C 文件中获取 char* 的最快方法
【发布时间】:2022-01-24 20:54:40
【问题描述】:

我有获取参数的函数:char* filedata(存储整个文件)和FILE *fp(打开的文件)。

void read_file(char *filedata, FILE *fp){
  char buffer[1000];

  while(fgets(buffer, sizeof(buffer), fp))
  {
    char *new_str;
    if((new_str = malloc(strlen(filedata) + strlen(buffer)+1)) != NULL)
    {
      new_str[0] = '\0'; // ensures the memory is an empty string
      strcat(new_str, filedata);
      strcat(new_str, buffer);
    }
    else
    {
      printf("malloc failed!\n");
    }
    strcpy(filedata, new_str);
  }

  fclose(fp);
}

但这并不太快……有没有更快的方法来读取整个文件?

【问题讨论】:

  • 您想阅读所有行,而不仅仅是简单的阅读。如果为真,请编辑标题。
  • “快速”定义不明确。快速打开文件?快速访问文件中的数据?什么是足够快的?有什么要求?
  • 对我来说看起来很泄漏:(
  • 您的问题的答案:是的。也就是说,除了惊人的内存泄漏之外,我认为您从未听说过Shlemiel, The Painter。仅供参考,无论谁为您的函数编写规范,他们都会从 gets 的所有错误中提取一页并将其体现在文件读取操作中。例如,一个指向目标数据的指针,没有任何关于实际存在多少空间的参考。这是厄运的秘诀。这也使其余的一切变得毫无意义。只需继续将数据转储到该目标中,随时推进filedata。那么……不要

标签: arrays c file optimization


【解决方案1】:

下面是我的函数,说明了我通常是如何做的。不确定它与所有其他可能的 C 实现相比有多快。但我想它们都非常相似,除非以一种或另一种方式编程不当,这可能会导致执行速度更慢、效率更低。

/* ==========================================================================
 * Function:    readfile ( FILE *ptr, int *nbytes )
 * Purpose:     read open file ptr into internal buffer
 * --------------------------------------------------------------------------
 * Arguments:   ptr (I)         FILE * to already open (via fopen)
 *                              file, whose contents are to be read
 *              nbytes (O)      int * returning #bytes in returned buffer
 * --------------------------------------------------------------------------
 * Returns:     ( unsigned char * )  buffer with ptr's contents
 * --------------------------------------------------------------------------
 * Notes:     o caller should free() returned output buffer ptr when finished
 * ======================================================================= */
/* --- entry point --- */
unsigned char *readfile ( FILE *ptr, int *nbytes ) {
  /* --- 
   * allocations and declarations
   * ------------------------------- */
  unsigned char *outbuff = NULL;        /* malloc'ed and realloc'ed below */
  int  allocsz=0, reallocsz=500000,     /*total #bytes allocated, #realloc */
       blksz=9900, nread=0,             /* #bytes to read, #actually read */
       buffsz = 0;                      /* total #bytes in buffer */
  /* ---
   * collect all bytes from ptr
   * ----------------------------- */
   if ( ptr != NULL ) {                 /* return NULL error if no input */
    while ( 1 ) {                       /* read all input from file */
      if ( buffsz+blksz + 99 >= allocsz ) { /* first realloc more memory */
        allocsz += reallocsz;           /*add reallocsz to current allocation*/
        if ( (outbuff=realloc(outbuff,allocsz)) == NULL ) /* reallocate */
          goto end_of_job; }            /* quit with NULL ptr if failed */
      nread = fread(outbuff+buffsz,1,blksz,ptr); /* read next block */
      if ( nread < 1 ) break;           /* all done, nothing left to read */
      buffsz += nread;                  /* add #bytes from current block */
      } /* --- end-of-while(1) --- */
    fclose(ptr);                        /* close fopen()'ed file ptr */
    } /* --- end-of-if(ptr!=NULL) --- */
  end_of_job:
    if ( nbytes != NULL ) *nbytes = buffsz; /* #bytes in outbuff */
    return ( outbuff );                 /* back to caller with output or NULL*/
  } /* --- end-of-function readfile() --- */

【讨论】:

  • 为什么不分配整个文件的大小?
  • @einpoklum 该函数通常从 popen("command","r") 而不是文件读取输出,我的实际函数包含第三个参数 int isclose 离开ptr open if 0, fclose's if 1, pclose's if 2. 所以我只能知道 fopen'ed ptr 的整个大小,而不是 popen'ed 的。
【解决方案2】:

有一些注意事项,您可以使用 fread() function 一口气将整个文件读入适当大小的缓冲区。

以下代码概述了如何打开文件、确定其大小、分配该大小的缓冲区,然后将文件的数据(全部)读入该缓冲区。但请注意关于 fseekftell 函数的注意事项(稍后讨论):

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

int main(void)
{
    char* filename = "MyFile.txt"; // Or whatever
    FILE* fp = fopen(filename, "rb"); // Open in binary mode
    int seek = fseek(fp, 0, SEEK_END); // CAVEAT: Files in BINARY mode may not support SEEK_END ...
    if (seek != 0) {
        printf("Cannot fseek on binary file!\n");
        fclose(fp);
        return 1;
    }
    size_t filesize = (size_t)ftell(fp); // ... but this is not reliable if opened in TEXT mode!
    char* filedata = calloc(filesize + 1, 1); // Add 1 for the terminating "nul" character
    rewind(fp);
    fread(filedata, 1, filesize, fp); // Read whole file
    
    // Clean up ...
    fclose(fp);
    free(filedata);

    return 0;
}

注意事项:

请注意,以 BINARY 模式打开的文件(如我在 fopen() 调用中给出的 "rb" 模式参数)在对 @987654333 的调用中支持 SEEK_END 源不是必需 @;如果您的平台是这种情况,那么this answer 提供了一些替代方法来确定文件的大小。 From cppreference:

... 二进制流不需要支持 SEEK_END,在 特别是如果输出额外的空字节。

但是,另一方面,以文本模式打开文件(使用"rt")将使对ftell 的调用实际上毫无意义,就输入缓冲区所需的大小和指定给@987654336 的值而言@; from cppreference:

如果流以文本模式打开,则返回的值 函数未指定,仅作为输入有意义 fseek().

还请注意,正如 cmets 中所指出的,如果文件大小大于可以存储在 long int 变量中的最大值,fseek()ftell() 函数将失败;要处理这种情况,您可以使用(取决于平台的)64 位等效项,正如我在 an answer I posted some time ago 中所述。

【讨论】:

  • 另外,int seek = fseek(fp, 0, SEEK_END); 会因为文件太大而无法包含在 int 中而出现问题。
  • @AndrewHenle 是的,确实如此。但是 fseekftell 的 64 位版本还没有(还没有?)跨平台/编译器标准化。 (实际上,导致问题的将是 ftell - fseek 函数调用不需要/使用过大的整数。)
  • @AndrewHenle 另请注意,fseekftell 使用 long int 偏移量/大小。但我添加了一个注释,链接到处理 BIG 文件的特定于平台的方法。
  • 没错,但long 在 Windows 系统上只有 4 个字节,即使是 64 位系统也是如此。
【解决方案3】:

注释您的函数(不提及泄漏等)并计算字符缓冲区上的操作:


void read_file(char *filedata, FILE *fp){
  char buffer[1000];

  while(fgets(buffer, sizeof(buffer), fp))      // <<-- NEW_SIZE
  {
    char *new_str;
    if((new_str = malloc(strlen(filedata)       // <<-- OLD_SIZE
         + strlen(buffer)                       // <<-- NEW_SIZE
        +1)) != NULL)
    {
      new_str[0] = '\0'; // ensures the memory is an empty string
      strcat(new_str, filedata);                // <<-- OLD_SIZE
      strcat(new_str, buffer);                  // <<-- OLD_SIZE + NEW_SIZE
    }
    else
    {
      printf("malloc failed!\n");
    }
    strcpy(filedata, new_str);                  // <<-- OLD_SIZE + NEW_SIZE
  }

  fclose(fp);
}

fgets()strlen()strcat()strcpy() 都需要遍历字符缓冲区。 实际上只需要fgets(),其余的复制都可以避免。

添加通过缓冲区的次数:

每个循环的操作总和:4 * OLD_SIZE + 4 * NEW_SIZE 并且:请记住,OLD_SIZE 实际上是 SUM(NEW_SIZE),递归的,所以你的函数有 QUADRATIC 行为 wrt 循环迭代的次数。(基本上是读取的行数)

所以你最终得到:

Number of times a character is inspected
    = 4 * N_LINE * LINE_SIZE
    + 8 * (NLINE * (NLINE-1) ) * LINE_SIZE
    ;

,这意味着对于一个 100 行的文件,您需要大约 40K 次遍历字符串。

[这是“画家施莱米尔”的故事]

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-03-13
    • 2011-04-18
    • 1970-01-01
    • 1970-01-01
    • 2018-10-25
    • 2012-02-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多