【问题标题】:Problems using fscanf with formatted text file将 fscanf 与格式化文本文件一起使用时出现问题
【发布时间】:2013-12-29 23:09:37
【问题描述】:

我对 C/C++ 比较陌生,目前(正在尝试)使用它将包含数字数据的大型格式化文本文件解析为数组,以便能够使用 LAPACK 库处理这些文件。 我正在解析的文本文件有一个非常简单的格式:一个 5 行标题,后跟 50 个值,下一个 5 行标题和 50 个值,重复大约。 100万次左右:

5 行标题
1.000000E+00 2.532093E+02
2.000000E+00 7.372978E+02
3.000000E+00 5.690047E+02

我目前的方法是使用 fscanf 函数,但我得到了奇怪的结果。我目前正在使用一种非常幼稚的方法来跳过包含标题文本的行,但我担心这可能是问题所在。或者我对 fscanf 的使用可能存在缺陷。这是我目前所拥有的:

int main() {
    FILE *ifp; 
    FILE *ofp;
    char mystring[500];
    int i,j,n;

  //ofp = fopen("newfile.txt","w");
    ifp = fopen("results","r");
    if (ifp != NULL) {
  //Test with 10 result blocks each containing 50 frequency values
    float** A = fmatrix(50,10);
    for (j=0; j<10; j++) {
        //fscanf(ifp, "%*[^\n]\n", NULL);
        //fscanf(ifp, "%*[^\n]\n", NULL);
        //fscanf(ifp, "%*[^\n]\n", NULL);
        //fscanf(ifp, "%*[^\n]\n", NULL);
        //fscanf(ifp, "%*[^\n]\n", NULL);

        //using fgets w/ printf to see contents of "discarded" lines
        fgets(mystring,500,ifp); printf("%s",mystring);
        fgets(mystring,500,ifp); printf("%s",mystring);
        fgets(mystring,500,ifp); printf("%s",mystring);
        fgets(mystring,500,ifp); printf("%s",mystring);
        fgets(mystring,500,ifp); printf("%s",mystring);

        for (i=0; i<50; i++) {
            //skip over first float, store the next float into A[i][j]
            n=fscanf(ifp," %*e %E", &A[i][j]);
            printf("A[%i][%i]: %E, %i\n",i,j,A[i][j],n);
        }
    }
}
return 0;
}

float** fmatrix(int m, int n) {
    //Return an m x n Matrix
    int i;
    float** A = (float**)malloc(m*sizeof(float*));
    A[0] = (float*)malloc(m*n*sizeof(float));
    for (i = 1; i < m; i++) {
        A[i] = A[i-1]+n;
    }
return A;
}  

我得到的结果很奇怪。我得到一个与结果文件匹配的 50 个分量列向量,然后得到 50 个零作为第二个列向量,第三个列向量对应于我的结果文件中的第二个值,依此类推。也就是说,我在矩阵中得到交替的零列和非零值。后来我插入了 fscanf 行以查看发生了什么,令我惊讶的是,一些被丢弃的行是包含数字数据的行,而不仅仅是标题行。

我希望有人可能知道这是什么,或者这里可能出了什么问题?由于这是一种如此简单的格式,我真的不知道问题出在哪里。另一个相关问题是:跳过标题文本的首选方法是什么?我使用的方法实际上只是一次性使用,因为头文件/文件格式的任何更改都会使代码变得毫无价值。也许使用 fgets 来检查格式是否与文件的数据部分匹配,并跳过任何与 2 列模式不匹配的行?

关于性能的最后一个问题:除了错误之外,fscanf 是在这里进行的最佳方式吗?正如我之前提到的,这些文件有时可能有几亿行的大小,而且我完全不熟悉 C/C++,不知道是否有更快的方法将如此大量的行读取到矩阵/向量中。

我希望我在此处提供了足够的信息以明确我的问题。如果需要,我可以在此处发布我的结果文件的摘录。

【问题讨论】:

  • C 还是 C++?猜测使用fscanf 是C。所以去掉标签
  • 标题是什么样的?如果是我,我更愿意检测标题行并丢弃它们,而不是假设文件始终处于完美格式。
  • 只是一个快速提示:fscanf,因为它可以在行尾运行,所以往往会使程序员感到困惑。将每一行读入字符串并在其上使用 sscanf 通常更容易。
  • Don't cast the return value of malloc()。二维数组优于指针指向指针:float (*a)[n] = malloc(sizeof(*a) * m);。不要使用scanf(),因为它会让人困惑——获取和解析用户输入的合理替代方法包括fgets()strstr()strtok_r()strtod()strtol()等。

标签: c scanf


【解决方案1】:

因为您没有始终如一地使用fgets(),所以您读取了 5 个标题行,然后是 50 个数字,但最后一个数字离开了第 55 行的换行符,准备被第一个 fgets() 或下一个标题块读取线。所以第二个标题读取块(仅)读取换行符,然后是 4 个标题行,然后数据扫描尝试将标题的最后一行作为数字读取并且(可能)失败。

始终检查每个输入函数的返回值(即使这似乎让生活变得痛苦)。

而且,我建议使用fgets() 阅读每一行。跳过标题行;使用sscanf() 转换数据线上的数据。但请检查 fgets()sscanf() 以获取正确的返回值。 还有其他函数可以将字符串转换为数字;可以使用strtod()

这里有一些工作代码,缩减为处理 5 个数据块,每组 10 行(仍然是 5 个标题行):

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

extern float **fmatrix(int m, int n);

enum { HDRS = 5, ROWS = 10, COLS = 5 };

static int read_line(FILE *ifp, char *buffer, size_t buflen)
{
    if (fgets(buffer, buflen, ifp) == 0)
    {
        fprintf(stderr, "EOF\n");
        return 0;
    }
    size_t len = strlen(buffer);
    buffer[len-1] = '\0';
    printf("[[%s]]\n", buffer);
    return 1;
}

int main(void)
{
    FILE *ifp;
    char mystring[500];
    int i, j, n;

    ifp = stdin;
    if (ifp != NULL)
    {
        // Test with COLS result blocks each containing ROWS frequency values
        float **A = fmatrix(ROWS, COLS);
        for (j = 0; j < COLS; j++)
        {
            // using fgets w/ printf to see contents of "discarded" lines
            for (i = 0; i < HDRS; i++)
            {
                if (read_line(ifp, mystring, sizeof(mystring)) == 0)
                    break;
            }

            for (i = 0; i < ROWS; i++)
            {
                // skip over first float, store the next float into A[i][j]
                if (read_line(ifp, mystring, sizeof(mystring)) == 0)
                    break;
                if ((n = sscanf(mystring, " %*e %E", &A[i][j])) != 1)
                    break;
                printf("A[%i][%i]: %E, %i\n", i, j, A[i][j], n);
            }
        }

        for (i = 0; i < ROWS; i++)
        {
            for (j = 0; j < COLS; j++)
                printf("%8.3f", A[i][j]);
            putchar('\n');
        }
    }
    return 0;
}

float **fmatrix(int m, int n)
{
    // Return an m x n Matrix
    int i;
    float **A = (float **)malloc(m * sizeof(float *));
    A[0] = (float *)malloc(m * n * sizeof(float));
    for (i = 1; i < m; i++)
    {
        A[i] = A[i - 1] + n;
    }
    return A;
}

较小的数据文件:

Line 1 of heading 1
Line 2 of heading 1
Line 3 of heading 1
Line 4 of heading 1
Line 5 of heading 1
18.1815 56.4442
12.0478 15.5530
47.7793 44.5291
30.8319 78.9396
53.5651 28.1290
74.9131 90.5912
34.9319 10.5254
69.7780 56.8633
92.5056 11.8101
82.0158 31.7586
Line 1 of heading 2
Line 2 of heading 2
Line 3 of heading 2
Line 4 of heading 2
Line 5 of heading 2
118.15 564.442
104.78 155.530
477.93 445.291
383.19 789.396
556.51 281.290
791.31 905.912
393.19 105.254
677.80 568.633
950.56 118.101
801.58 317.586
Line 1 of heading 3
Line 2 of heading 3
Line 3 of heading 3
Line 4 of heading 3
Line 5 of heading 3
18.1815 36.4442
12.0478 35.5530
47.7793 34.5291
30.8319 38.9396
53.5651 38.1290
74.9131 30.5912
34.9319 30.5254
69.7780 36.8633
92.5056 31.8101
82.0158 31.7586
Line 1 of heading 4
Line 2 of heading 4
Line 3 of heading 4
Line 4 of heading 4
Line 5 of heading 4
118.15 464.442
104.78 455.530
477.93 445.291
383.19 489.396
556.51 481.290
791.31 405.912
393.19 405.254
677.80 468.633
950.56 418.101
801.58 417.586
Line 1 of heading 5
Line 2 of heading 5
Line 3 of heading 5
Line 4 of heading 5
Line 5 of heading 5
118.15 564.442
104.78 555.530
477.93 545.291
383.19 589.396
556.51 581.290
791.31 505.912
393.19 505.254
677.80 568.633
950.56 518.101
801.58 517.586

请注意,20 个随机数的块以不同的方式编辑,以在每个块中获得不同的数字。不过,块中的值之间存在很强的遗传相似性。

在数据文件上运行程序的结果。

[[Line 1 of heading 1]]
[[Line 2 of heading 1]]
[[Line 3 of heading 1]]
[[Line 4 of heading 1]]
[[Line 5 of heading 1]]
[[18.1815 56.4442]]
A[0][0]: 5.644420E+01, 1
[[12.0478 15.5530]]
A[1][0]: 1.555300E+01, 1
[[47.7793 44.5291]]
A[2][0]: 4.452910E+01, 1
[[30.8319 78.9396]]
A[3][0]: 7.893960E+01, 1
[[53.5651 28.1290]]
A[4][0]: 2.812900E+01, 1
[[74.9131 90.5912]]
A[5][0]: 9.059120E+01, 1
[[34.9319 10.5254]]
A[6][0]: 1.052540E+01, 1
[[69.7780 56.8633]]
A[7][0]: 5.686330E+01, 1
[[92.5056 11.8101]]
A[8][0]: 1.181010E+01, 1
[[82.0158 31.7586]]
A[9][0]: 3.175860E+01, 1
[[Line 1 of heading 2]]
[[Line 2 of heading 2]]
[[Line 3 of heading 2]]
[[Line 4 of heading 2]]
[[Line 5 of heading 2]]
[[118.15 564.442]]
A[0][1]: 5.644420E+02, 1
[[104.78 155.530]]
A[1][1]: 1.555300E+02, 1
[[477.93 445.291]]
A[2][1]: 4.452910E+02, 1
[[383.19 789.396]]
A[3][1]: 7.893960E+02, 1
[[556.51 281.290]]
A[4][1]: 2.812900E+02, 1
[[791.31 905.912]]
A[5][1]: 9.059120E+02, 1
[[393.19 105.254]]
A[6][1]: 1.052540E+02, 1
[[677.80 568.633]]
A[7][1]: 5.686330E+02, 1
[[950.56 118.101]]
A[8][1]: 1.181010E+02, 1
[[801.58 317.586]]
A[9][1]: 3.175860E+02, 1
[[Line 1 of heading 3]]
[[Line 2 of heading 3]]
[[Line 3 of heading 3]]
[[Line 4 of heading 3]]
[[Line 5 of heading 3]]
[[18.1815 36.4442]]
A[0][2]: 3.644420E+01, 1
[[12.0478 35.5530]]
A[1][2]: 3.555300E+01, 1
[[47.7793 34.5291]]
A[2][2]: 3.452910E+01, 1
[[30.8319 38.9396]]
A[3][2]: 3.893960E+01, 1
[[53.5651 38.1290]]
A[4][2]: 3.812900E+01, 1
[[74.9131 30.5912]]
A[5][2]: 3.059120E+01, 1
[[34.9319 30.5254]]
A[6][2]: 3.052540E+01, 1
[[69.7780 36.8633]]
A[7][2]: 3.686330E+01, 1
[[92.5056 31.8101]]
A[8][2]: 3.181010E+01, 1
[[82.0158 31.7586]]
A[9][2]: 3.175860E+01, 1
[[Line 1 of heading 4]]
[[Line 2 of heading 4]]
[[Line 3 of heading 4]]
[[Line 4 of heading 4]]
[[Line 5 of heading 4]]
[[118.15 464.442]]
A[0][3]: 4.644420E+02, 1
[[104.78 455.530]]
A[1][3]: 4.555300E+02, 1
[[477.93 445.291]]
A[2][3]: 4.452910E+02, 1
[[383.19 489.396]]
A[3][3]: 4.893960E+02, 1
[[556.51 481.290]]
A[4][3]: 4.812900E+02, 1
[[791.31 405.912]]
A[5][3]: 4.059120E+02, 1
[[393.19 405.254]]
A[6][3]: 4.052540E+02, 1
[[677.80 468.633]]
A[7][3]: 4.686330E+02, 1
[[950.56 418.101]]
A[8][3]: 4.181010E+02, 1
[[801.58 417.586]]
A[9][3]: 4.175860E+02, 1
[[Line 1 of heading 5]]
[[Line 2 of heading 5]]
[[Line 3 of heading 5]]
[[Line 4 of heading 5]]
[[Line 5 of heading 5]]
[[118.15 564.442]]
A[0][4]: 5.644420E+02, 1
[[104.78 555.530]]
A[1][4]: 5.555300E+02, 1
[[477.93 545.291]]
A[2][4]: 5.452910E+02, 1
[[383.19 589.396]]
A[3][4]: 5.893960E+02, 1
[[556.51 581.290]]
A[4][4]: 5.812900E+02, 1
[[791.31 505.912]]
A[5][4]: 5.059120E+02, 1
[[393.19 505.254]]
A[6][4]: 5.052540E+02, 1
[[677.80 568.633]]
A[7][4]: 5.686330E+02, 1
[[950.56 518.101]]
A[8][4]: 5.181010E+02, 1
[[801.58 517.586]]
A[9][4]: 5.175860E+02, 1
  56.444 564.442  36.444 464.442 564.442
  15.553 155.530  35.553 455.530 555.530
  44.529 445.291  34.529 445.291 545.291
  78.940 789.396  38.940 489.396 589.396
  28.129 281.290  38.129 481.290 581.290
  90.591 905.912  30.591 405.912 505.912
  10.525 105.254  30.525 405.254 505.254
  56.863 568.633  36.863 468.633 568.633
  11.810 118.101  31.810 418.101 518.101
  31.759 317.586  31.759 417.586 517.586

【讨论】:

  • 非常感谢! fscanf 的(明显的)简单性吸引了我,但也给我带来了麻烦。逐行读取并始终检查将来的返回值!
  • @user999318:智慧之言……是的,这通常使生活更可预测。 scanf()fscanf() 很难可靠地使用,而且当你不知道他们吃了多少行空白,或者他们在给出之前设法转换了多少输入时,它们也很难很好地报告错误出现错误。使用fgets() 或亲戚,您至少可以在错误消息中显示整行输入。
  • @Jonathan Leffler 在某些情况下,fgets() 返回一个"" 或一个不以'\n' 结尾的字符串。你看到if (len &gt; 0 &amp;&amp; buffer[len-1] == '\n') buffer[--len] = '\0'; 的价值了吗?
  • 你是对的,我很懒惰。它唯一可以返回零长度字符串的时间是 EOF 或错误,因此 len &gt; 0 检查应该是无关紧要的(除非您传递大小为 0 或 1 的缓冲区,但我对非白痴给予了一些信任)。如果这些行过长,那么我将删除最后一个读取的字符,并将剩余部分视为第二行(然后是第三、第四、...)行。归结为您对自己的输入的信任程度,以及如果您猜错了会造成多大的损失。对于这种情况,一个 500 字节的缓冲区将毫无困难地存储任何格式合理的数字对。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-09-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-11-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多