从大文件中读取数据到C中的struct

时间:2019-03-07 22:00:01

标签: c struct

我是C编程的初学者。我需要使用文件中的struct有效地从文件中读取数百万个数据。以下是输入文件的示例。

2,33.1609992980957,26.59000015258789,8.003999710083008
5,15.85200023651123,13.036999702453613,31.801000595092773
8,10.907999992370605,32.000999450683594,1.8459999561309814
11,28.3700008392334,31.650999069213867,13.107999801635742

我有下面显示的当前代码,它给出了错误“文件错误” 建议文件为NULL,但文件包含数据。

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

struct O_DATA
{
    int index;
    float x;
    float y;
    float z;
};

int main ()
{
    FILE *infile ;
    struct O_DATA input;
    infile = fopen("input.dat", "r");
    if (infile == NULL);
    {
            fprintf(stderr,"\nError file\n");
            exit(1);
    }
    while(fread(&input, sizeof(struct O_DATA), 1, infile))
            printf("Index = %d X= %f Y=%f Z=%f", input.index , input.x ,   input.y , input.z);
    fclose(infile);
    return 0;
}

我需要有效地从输入文件中读取和存储数据以进行进一步处理。任何帮助将非常感激。提前感谢。 〜

5 个答案:

答案 0 :(得分:1)

if (infile == NULL);
{ /* floating block */ }

以上if是一个完整的语句,无论infile的值是什么都不做。无论infile包含什么内容,都将执行“浮动”块。
删除分号以将“浮动”块“附加”到if

if (infile == NULL)
{ /* if block */ }

答案 1 :(得分:1)

;测试后,您的if (infile == NULL)错误,请尝试删除该错误...

[编辑:第二秒9秒! :-)]

答案 2 :(得分:1)

首先弄清楚如何将一行文本转换为数据

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

struct my_data
{
  unsigned int index;
  float x;
  float y;
  float z;
};



struct my_data *
deserialize_data(struct my_data *data, const char *input, const char *separators)
{
  char *p;                      
  struct my_data tmp;

  if(sscanf(input, "%d,%f,%f,%f", &data->index, &data->x, &data->y, &data->z) != 7)
    return NULL;
  return data;
}

deserialize_data(struct my_data *data, const char *input, const char *separators)
{
  char *p;                      
  struct my_data tmp;
  char *str = strdup(input);    /* make a copy of the input line because we modify it */

  if (!str) {               /* I couldn't make a copy so I'll die */
      return NULL;
  }

  p = strtok (str, separators); /* use line for first call to strtok */
  if (!p) goto err;
  tmp.index = strtoul (p, NULL, 0);         /* convert text to integer */

  p = strtok (NULL, separators);    /* strtok remembers line */
  if (!p) goto err;
  tmp.x = atof(p);

  p = strtok (NULL, separators);
  if (!p) goto err;
  tmp.y = atof(p);

  p = strtok (NULL, separators);
  if (!p) goto err;
  tmp.z = atof(p);

  memcpy(data, &tmp, sizeof(tmp)); /* copy values out */
  goto out;

err:
  data = NULL;

out:
  free (str);
  return data;
}

int main() {
    struct my_data somedata;
    deserialize_data(&somedata, "1,2.5,3.12,7.955", ",");
    printf("index: %d, x: %2f, y: %2f, z: %2f\n", somedata.index, somedata.x, somedata.y, somedata.z);
}

通过读取文件中的行将其合并:

只是此处的主要功能(插入上一个示例中的其余功能)

   int
   main(int argc, char *argv[])
   {
       FILE *stream;
       char *line = NULL;
       size_t len = 0;
       ssize_t nread;
       struct my_data somedata;

       if (argc != 2) {
           fprintf(stderr, "Usage: %s <file>\n", argv[0]);
           exit(EXIT_FAILURE);
       }

       stream = fopen(argv[1], "r");
       if (stream == NULL) {
           perror("fopen");
           exit(EXIT_FAILURE);
       }

       while ((nread = getline(&line, &len, stream)) != -1) {
           deserialize_data(&somedata, line, ",");
           printf("index: %d, x: %2f, y: %2f, z: %2f\n", somedata.index, somedata.x, somedata.y, somedata.z);
       }

       free(line);
       fclose(stream);
       exit(EXIT_SUCCESS);
   }

答案 3 :(得分:1)

您已经对语法/ structs / etc有了可靠的答复,但是我将提供另一种读取文件本身中数据的方法:我喜欢Martin York的CSVIterator解决方案。这是我处理CSV的首选方法,因为它需要更少的代码来实现,并且具有易于修改的额外好处(即,您可以根据需要编辑CSVRow和CSVIterator定义)。

这是一个使用Martin未经编辑的代码而不包含结构或类的最完整的示例。在我看来,尤其是对于初学者而言,以更简单的方法开始开发代码更加容易。随着代码开始成形,您将更清楚为什么以及在何处需要实现更多抽象/高级设备。

请注意,由于我使用std::stod(从技术上讲,这将需要使用C ++ 11或更高版本进行编译)(也许我也忘记了其他一些东西),因此请考虑以下因素:

//your includes
//...
#include"wherever_CSVIterator_is.h"

int main (int argc, char* argv[]) 
{
  int index;
  double tmp[3]; //since we know the shape of your input data
  std::vector<double*> saved = std::vector<double*>();
  std::vector<int> indices;

  std::ifstream file(argv[1]);
  for (CSVIterator loop(file); loop != CSVIterator(); ++loop) { //loop over rows
    index = (*loop)[0]; 
    indices.push_back(index); //store int index first, always col 0
    for (int k=1; k < (*loop).size(); k++) {                    //loop across columns
       tmp[k-1] = std::stod((*loop)[k]); //save double values now
    }
    saved.push_back(tmp);
  }

 /*now we have two vectors of the same 'size'
  (let's pretend I wrote a check here to confirm this is true), 
  so we loop through them together and access with something like:*/

  for (int j=0; j < (int)indices.size(); j++) {
    double* saved_ptr = saved.at(j); //get pointer to first elem of each triplet
    printf("\nindex: %g |", indices.at(j));
    for (int k=0; k < 3; k++) {
      printf(" %4.3f ", saved_ptr[k]);
    }
    printf("\n");
  }
}

编写时很少大惊小怪,但更危险(如果save []超出范围,则有麻烦)。还存在一些不必要的复制,但是我们受益于使用std :: vector容器,而不是确切知道我们需要分配多少内存。

答案 4 :(得分:0)

不提供输入文件的示例至少在纸上或评论中指定您的输入file format ,例如以EBNF表示法(因为您的示例是 text ...它不是 一个binary file)。确定数字是否必须位于不同的行中(或者您是否可以接受由百万字节组成的单行大文件;请阅读Comma Separated Values格式)。然后,为该格式编写一些parser。对于您来说,很简单的recursive descent parsing就足够了(并且您的特定解析器甚至不会使用recursion)。

详细了解<stdio.h> and its routines。花时间仔细阅读该文档。由于您输入的是文本,而不是 binary ,因此您不需要fread。请注意,输入例程可能会失败,因此您应该处理失败情况。

当然,fopen可能会失败(例如,因为您的working directory不是您认为的那样)。您最好使用perrorerrno来查找有关失败原因的更多信息。所以至少是代码:

infile = fopen("input.dat", "r");
if (infile == NULL) {
  perror("fopen input.dat");
  exit(EXIT_FAILURE);
}

请注意,分号(或分号的不存在)在C语言中非常重要(if后没有分号)。再次阅读C language的基本语法。了解有关How to debug small programs的信息。编译时启用所有警告和调试信息(使用GCC,至少使用gcc -Wall -g进行编译)。编译器警告非常有用!

请记住,fscanf对行尾(换行符)的处理与空格字符不同。因此,如果输入必须具有不同的,则需要分别阅读每一行。

您可能会使用fgets(或getline)读取每条,并分别解析每行。您可以在sscanf的帮助下进行解析(也许%n可能有用)-并且您想使用sscanf的返回计数。您也可以使用strtok和/或strtod进行这样的解析。

请确保您的解析和整个程序正确无误。对于当前的计算机(速度非常快,并且大多数时候您的输入文件位于page cache中),它很可能足够快。一百万行可以很快读取(如果在Linux上,您可以将解析时间与wc用来计算文件行的时间进行比较)。在我的计算机上(具有AMD2970WX处理器的功能强大的Linux桌面-它具有很多内核,但是您的程序仅使用一个,64 GB的RAM和SSD磁盘),可以用更少的时间(wc)读取一百万行少于30毫秒,所以我想如果给定一百万行输入,并且进一步的处理很简单(线性时间),则整个程序应在不到半秒的时间内运行。

您可能会填充struct O_DATA的大型数组,并且该数组可能应该动态分配,并在需要时重新分配。进一步了解C dynamic memory allocation。仔细阅读有关C memory management routines的信息。它们可能会失败,因此您需要处理该失败(即使不太可能发生)。您当然不想在每个循环中重新分配该数组。您可能可以在一些geometrical progression中分配它(例如,如果该数组的大小为size,则您将为某些{{1}调用realloc或新的malloc }仅当旧的int newsize = 4*size/3 + 10;太小时)。当然,您的数组通常会比实际需要的大一些,但是内存非常便宜,您可以“丢掉”其中的一些。

但是StackOverflow不是 一个“做我的作业”网站。我在上面给出了一些建议,但是您应该做功课。