【问题标题】:Getting data from file stream in c从c中的文件流中获取数据
【发布时间】:2011-04-12 13:00:37
【问题描述】:

我正在尝试从 FILE 指针中检索数据并放入字符串中。确定字符串缓冲区大小的最佳方法是什么?

char string[WHAT_SIZE?];
FILE *fp;
fp = fopen("info.dat", "r");

fgets(string, sizeof string, fp);

我是否将缓冲区大小设置为我认为适合该特定文件的大小?或者有没有更有效的方法来做到这一点,而不使用不可变缓冲区大小的字符串?

【问题讨论】:

  • 感谢您的回答,但 fread 用于二进制文件,而我的文件是带有“|”的简单 ASCII 文件分隔符。
  • 您可以在文本文件上使用fread()。它只是根据请求读取尽可能多的字节(大小 * n 个元素)。
  • Paynter 在处理文件时应该始终使用二进制模式。 “文本”模式是一种可憎的东西,它只会导致比解决问题更多的问题。

标签: c buffer filestream


【解决方案1】:

一般来说,您只需要选择一个尺码并配合它。基于最大预期行长度或记录长度或特定于输入类型的内容进行选择。只要确保检查返回码并处理线路比您预期的长的情况。

您可以使用一些技巧来获得准确的尺寸,但我不记得在实践中曾经使用过这些技巧:

  1. 执行 ftell,逐个字符读取,计数直到到达换行符,然后分配足够的内存,fseek 倒带,并读取整行。

  2. 执行 fseek 到文件末尾以查找大小,然后倒带并立即将整个内容读取到单个缓冲区中。

【讨论】:

  • 这在我看来是个坏建议。当您可以适应文件的实际内容时,为什么要“选择大小并使用它”?我认为最好进行临时调整而不是像这里描述的那样回溯,这样(例如)你就可以获取不是来自可搜索文件的输入。
  • 这取决于你在做什么。大量的实际工作程序都有一个固定的输入缓冲区。在输入进入系统后,他们可以将其复制到一个大小合适的分配字符串,或者甚至只是动态解析它并用下一行覆盖它。只要你对那些不适合的线条有一个好的策略,这是完全合理的。
  • 我建议,为那些不适合的线条制定一个好的策略可能比在每一种情况下安排线条适合但不足以证明彻底失败的病态要困难得多。是的,当然很多实际的工作问题都有一个固定的输入缓冲区——其中很多容易受到缓冲区溢出的影响,或者对他们将接受的输入有任意和不必要的限制,或者两者兼而有之。 (大量的实际工作程序也充满了错误,但这并不意味着我们应该尽量避免它们!)
【解决方案2】:

简单直接的方法是使用fseek()ftell()。获取文件大小后,为数据分配缓冲区并用fread()读入文件。

此示例是检索文件确切大小的一种非常常用的方法。

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

/* excepts file stream which is already opened */
long get_filesize(FILE *fp)
{
    long filesize;

    if( fseek(fp, 0, SEEK_END) ) != 0)
        exit(EXIT_FAILURE); /* exit with errorcode if fseek() fails */

    filesize = ftell(fp);

    rewind(fp);

    return filesize;
}

int main(void)
{
    FILE *fp;
    long filesize;
    unsigned char *buffer;

    fp = fopen("info.dat", "rb");

    filesize = get_filesize(fp);
    if(filesize < 1) exit(EXIT_FAILURE);

    buffer = malloc( filesize * sizeof(unsigned char) );
    if(buffer == NULL) exit(EXIT_FAILURE);

    /* checking the fread return value is not necessary but recommended */
    if((fread(buffer, sizeof(unsigned char), filesize, fp)) != filesize)
        exit(EXIT_FAILURE);

    fclose(fp);

    /* ===== use the file here ===== */

    free(buffer); /* remember to free the memory */

    return EXIT_SUCCESS;
}

【讨论】:

    【解决方案3】:

    如果您实际上打算从文件中读取 lines(这是使用 fgets 而不是 fread 的常见原因),那么您需要的是缓冲区长到足以容纳一条线。您通常无法提前知道,因此请使用 malloc(或 new,如果您使用 C++,但在这种情况下您可能会更好地使用 C++ 的 I/O 工具)动态分配它,并在你跑过一条太长的线。像这样的:

    size_t line_size = 256; /* reasonable initial default */
    char * line_buffer = malloc(line_size);
    line_buffer[line_size-2] = '\n'; /* yes, 2 */
    /* You should check for malloc failure here */
    while (whatever) {
      /* ... */
      fgets(line_buffer, line_size, fp); /* should check for failure and EOF here too */
      while (line_buffer[line_size-2] != '\n') {
        /* we filled the buffer, and the last character wasn't a newline */
        size_t new_line_size = 2*line_size;
        line_buffer = realloc(line_buffer, new_line_size); /* should check for failure here */
        line_buffer[new_line_size-2] = '\n';
        fgets(line_buffer+line_size-1, new_line_size-line_size+1, fp); /* should check for failure and EOF */
        line_size = new_line_size;
      }
      /* ... */
    }
    

    (警告:完全未经测试的代码;可能完全由错误和有毒废物组成。当然没有真正的代码应该有的所有错误条件测试。)

    如果某些白痴给你一个文件非常长的行,你可能会被建议不要让缓冲区无限制地增长;在某个时候放弃。您可能很想将上述行为封装到一个函数中,特别是如果您有多个代码位在做同样的事情。在这种情况下,您可能还希望将其状态(缓冲区及其当前大小)也封装到 struct 中。 (或者,如果您使用的是 C++,一个类,其中的缓冲区扩展读取将是一个成员函数。但是,再次,如果您使用的是 C++,那么您可能应该使用它已经为此提供的工具.)

    【讨论】:

      【解决方案4】:

      一种可能性是动态分配缓冲区,然后根据需要增加它(例如,使用 realloc)。这可能需要为 fgets 编写一个包装函数,以检查它是否读取了整行(换行符存储在缓冲区中)。它还必须处理 EOF 条件。

      这可能不言而喻,但使用 C 读取和解析具有可变宽度数据的文本文件是一项相当多的工作。这可能对您的情况没有意义,甚至是不可能的,但如果您可以使用 Ruby、Python、Perl、Awk 等,您可能可以在很短的时间内完成任务。您可以使用这些工具在几行代码中完成可能需要一百行 C 的代码。它们非常适合读取和解析分隔的文本文件。例如,下面的 ruby​​ 块逐行读取文本文件并用竖线将其分割:

      File.open("myfile.txt") { |file|
         while ( line = file.gets )
             puts "line: #{line}"
             a = line.split( /\|/ )
             puts "array: #{a}"
         end
      }
      

      只是为了好玩,这里有一个可能的实现,需要处理一些 TBD(错误检查)。主要问题(除了我没有看到的细微错误)将是解决缓冲区的释放问题,如果您没有完全阅读到 EOF。

      int myReadLine   // return non-zero if line returned, 0 on eof (see tbd below)
      (
         FILE *fp,     // (I) open file handle for reading
         char **buf,   // (IO) buffer allocated by this function.  It is freed by
                       // this function when EOF is hit.  TBD: Should write a myFreeLine
                       // (for encapsulation purposes) to free this buffer for cases where
                       // you quit calling
         int  *len     // (IO) current length of buffer pointed to by buf
      )
      {
         char *ret;
         char *pos;
         int  curlen;
         int  remaining;
      
         if ( *len == 0 )
            {
            assert( *buf == NULL );
            // pick a number out of the air.  Could be app-specific.  In debug
            // it may be nice to start very small to force reallocs to exercise all
            // code paths.
            *len = 2;
            // tbd: need error checking
            *buf = (char*)malloc( *len * sizeof( char ));
            }
      
         pos = *buf;
         remaining = *len;
      
         while ( 1 )
            {
            ret = fgets( pos, remaining, fp );
            if ( ret == NULL )
               {
               // tbd: should check if error occurred here.  For now assuming eof
               free( *buf );
               *buf = NULL;
               *len = 0;
               return 0;
               }
      
            // check to see if we got the entire line.
            curlen = strlen( *buf );
            if ( (*buf)[curlen - 1] == '\n' )  // tbd:  check for \r?
               {
               // apparently we got the whole line
               // remove the end of line (at least that's what I would want)
               (*buf)[curlen - 1] = '\0';
               return 1;
               }
            else
               {
               // failed to get entire line
               assert( curlen + 1 == *len );
      
               // grow the buffer (tbd: realloc is a pain ... need error checking)
               *len *= 2;  // doubling is often a good plan
               *buf = (char*)realloc( *buf, *len );
      
               // set the "amount left" variables correctly for next iteration
               remaining = *len - curlen;
               pos = *buf + curlen;
               }
            }  // while forever
      
         // don't expect to get here
         assert( 0 );
      
      }
      

      这是一个示例调用:

      void readfile(char *filepath)
      {
         int len = 0;
         char *buf = NULL;
      
      
         FILE *fp=fopen(filepath,"rt");
         while ( myReadLine( fp, &buf, &len  ))
            printf( "'%s'\n", buf );
         fclose(fp);
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-11-10
        • 2015-07-30
        • 1970-01-01
        • 2015-02-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多