【问题标题】:Reading a stream of values from text file in C从 C 中的文本文件中读取值流
【发布时间】:2015-07-06 13:03:58
【问题描述】:

我有一个文本文件,其中可能包含一个或最多 400 个数字。每个数字用逗号分隔,分号用于表示数字流的结束。 目前我正在使用 fgets 逐行读取文本文件。出于这个原因,我使用了一个包含 1024 个元素的固定数组(文本文件每行的最大字符数)。 这不是实现这一点的理想方式,因为如果在文本文件中只输入一个数字,那么 1024 个元素的数组将毫无意义。 有没有办法通过 malloc 函数(或任何其他方法)使用 fgets 来提高内存效率?

【问题讨论】:

  • 拥有一个在最坏情况下额外增加 1023 个字节的静态缓冲区在大多数情况下可能比增加代码复杂性和动态内存管理的开销更好。
  • 您是否在内存小于 64K 字节的系统上运行代码?如果答案是否定的,那么就没有理由考虑这个。
  • 注意,顺便说一句,如果一行正好有 1024 个字符长,那么你需要一个 1026 个字符的数组来保存所有内容,再加上一个换行符和一个字符串终结者。不过,最好还是将其声明为固定长度的本地数组。

标签: c arrays file text malloc


【解决方案1】:

如果您正在考虑在生产代码中使用它,那么我会要求您遵循 cmets 部分中的建议。

但是如果您的要求更多是为了学习或学校,那么这里有一个复杂的方法。

伪代码

1. Find the size of the file in bytes, you can use "stat" for this.
2. Since the file format is known, from the file size, calculate the number of items.
3. Use the number of items to malloc.

瞧! :p

如何查找文件大小

你可以使用stat,如下图:

#include <sys/stat.h>
#include <stdio.h>

int main(void)
{
    struct stat st;

    if (stat("file", &st) == 0) {
        printf("fileSize: %d  No. of Items: %d\n", (st.st_size), (st.st_size/2));
        return st.st_size;
    }

    printf("failed!\n");
    return 0;
}

这个文件在运行时会返回文件大小:

$> cat file
1;
$> ./a.out
fileSize: 3  No. of Items: 1

$> cat file
1,2,3;
$> ./a.out
fileSize: 7  No. of Items: 3

免责声明:这种最小化预分配内存的方法是最佳方法吗?天堂里没有办法! :)

【讨论】:

  • 谁说行长是固定的?我知道每行可能有不同的长度。而您的第 2 点“计算项目数量”是不可能的。
  • @i486:关于行长的好点子!我假设每个数字都在同一行,因为问题中的这个语句每个数字都用逗号分隔,分号用于表示数字流的结束。并且为了保护第 2 点,如果数字流都在同一行中(即,如果它们之间没有“\n”)并且只是用“,”分隔并以“;”结尾然后我已经展示了如何计算项目数。在什么情况下你预见到这种失败?我很好奇。
【解决方案2】:

为您的数据动态分配空间是使用 C 语言工作的基本工具。您不妨付出代价来学习。首先要记住的是,

"如果你分配内存,你有责任跟踪它的使用 并保留一个指向块的起始地址的指针 内存,这样你就可以在完成后释放它。否则你的 像筛子一样泄漏内存的代码。”

动态分配是直截了当的。您分配一些初始内存块并跟踪您添加的内容。您必须测试每个分配是否成功。您必须测试您使用了多少内存块,并在已满时重新分配或停止写入数据,以防止写入超出内存块的末尾。如果你没有测试任何一个,你将破坏与你的代码相关的内存。

重新分配时,请始终使用临时指针重新分配,因为重新分配失败时,原始内存块被释放。 (导致该块中所有先前数据的丢失)。使用临时指针可以让您在需要时以保留该块的方式处理故障。

考虑到这一点,下面我们最初为 64 个long 值分配空间(您可以轻松更改为处理任何类型的代码,例如intfloatdouble...)。然后代码读取每一行数据(使用getline 为每一行动态分配缓冲区)。 strtol 用于解析将值分配给array 的缓冲区。 idx 用作跟踪已读取的值的索引,当idx 达到当前的nmax 时,array 重新分配为以前的两倍,nmax 更新为反映变化。对文件中的每一行数据继续进行读取、解析、检查和重新分配。完成后,这些值将打印到标准输出,显示从格式化为353,394,257,...293,58,135;的测试文件中读取的 400 个随机值

为了保持读取循环逻辑清晰,我已将strtol 转换的错误检查放入函数xstrtol,但如果您愿意,您可以随意将该代码包含在main() 中。这同样适用于realloc_long 函数。要查看重新分配发生的时间,您可以使用 -DDEBUG 定义编译代码。例如:

gcc -Wall -Wextra -DDEBUG -o progname yoursourcefile.c

程序需要您的数据文件名作为第一个参数,您可以提供一个可选的转换基数作为第二个参数(默认为 10)。例如:

./progname datafile.txt [base (default: 10)]

查看它,测试它,如果您有任何问题,请告诉我。

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

#define NMAX 64

long xstrtol (char *p, char **ep, int base);
long *realloc_long (long *lp, unsigned long *n);

int main (int argc, char **argv)
{

    char *ln = NULL;                /* NULL forces getline to allocate  */
    size_t n = 0;                   /* max chars to read (0 - no limit) */
    ssize_t nchr = 0;               /* number of chars actually read    */
    size_t idx = 0;                 /* array index counter              */
    long *array = NULL;             /* pointer to long                  */
    unsigned long nmax = NMAX;      /* initial reallocation counter     */
    FILE *fp = NULL;                /* input file pointer               */
    int base = argc > 2 ? atoi (argv[2]) : 10; /* base (default: 10)    */

    /* open / validate file */
    if (!(fp = fopen (argv[1], "r"))) {
        fprintf (stderr, "error: file open failed '%s'.", argv[1]);
        return 1;
    }

    /* allocate array of NMAX long using calloc to initialize to 0 */
    if (!(array = calloc (NMAX, sizeof *array))) {
        fprintf (stderr, "error: memory allocation failed.");
        return 1;
    }

    /* read each line from file - separate into array       */
    while ((nchr = getline (&ln, &n, fp)) != -1)
    {
        char *p = ln;      /* pointer to ln read by getline */ 
        char *ep = NULL;   /* endpointer for strtol         */

        while (errno == 0)
        {   /* parse/convert each number in line into array */
            array[idx++] = xstrtol (p, &ep, base);

            if (idx == nmax)        /* check NMAX / realloc */
                array = realloc_long (array, &nmax);

            /* skip delimiters/move pointer to next digit   */
            while (*ep && *ep != '-' && (*ep < '0' || *ep > '9')) ep++;
            if (*ep)
                p = ep;
            else
                break;
        }
    }

    if (ln) free (ln);              /* free memory allocated by getline */
    if (fp) fclose (fp);            /* close open file descriptor       */

    int i = 0;
    for (i = 0; i < idx; i++)
        printf (" array[%d] : %ld\n", i, array[i]);

    free (array);

    return 0;
}

/* reallocate long pointer memory */
long *realloc_long (long *lp, unsigned long *n)
{
    long *tmp = realloc (lp, 2 * *n * sizeof *lp);
#ifdef DEBUG
    printf ("\n  reallocating %lu to %lu\n", *n, *n * 2);
#endif
    if (!tmp) {
        fprintf (stderr, "%s() error: reallocation failed.\n", __func__);
        // return NULL;
        exit (EXIT_FAILURE);
    }
    lp = tmp;
    memset (lp + *n, 0, *n * sizeof *lp); /* memset new ptrs 0 */
    *n *= 2;

    return lp;
}

long xstrtol (char *p, char **ep, int base)
{
    errno = 0;

    long tmp = strtol (p, ep, base);

    /* Check for various possible errors */
    if ((errno == ERANGE && (tmp == LONG_MIN || tmp == LONG_MAX)) ||
        (errno != 0 && tmp == 0)) {
        perror ("strtol");
        exit (EXIT_FAILURE);
    }

    if (*ep == p) {
        fprintf (stderr, "No digits were found\n");
        exit (EXIT_FAILURE);
    }

    return tmp;
}

示例输出(使用 -DDEBUG 显示重新分配)

$ ./bin/read_long_csv dat/randlong.txt

  reallocating 64 to 128

  reallocating 128 to 256

  reallocating 256 to 512
 array[0] : 353
 array[1] : 394
 array[2] : 257
 array[3] : 173
 array[4] : 389
 array[5] : 332
 array[6] : 338
 array[7] : 293
 array[8] : 58
 array[9] : 135
<snip>
 array[395] : 146
 array[396] : 324
 array[397] : 424
 array[398] : 365
 array[399] : 205

内存错误检查

$ valgrind ./bin/read_long_csv dat/randlong.txt
==26142== Memcheck, a memory error detector
==26142== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al.
==26142== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
==26142== Command: ./bin/read_long_csv dat/randlong.txt
==26142==

  reallocating 64 to 128

  reallocating 128 to 256

  reallocating 256 to 512
 array[0] : 353
 array[1] : 394
 array[2] : 257
 array[3] : 173
 array[4] : 389
 array[5] : 332
 array[6] : 338
 array[7] : 293
 array[8] : 58
 array[9] : 135
<snip>
 array[395] : 146
 array[396] : 324
 array[397] : 424
 array[398] : 365
 array[399] : 205
==26142==
==26142== HEAP SUMMARY:
==26142==     in use at exit: 0 bytes in 0 blocks
==26142==   total heap usage: 7 allocs, 7 frees, 9,886 bytes allocated
==26142==
==26142== All heap blocks were freed -- no leaks are possible
==26142==
==26142== For counts of detected and suppressed errors, rerun with: -v
==26142== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 2 from 2)

【讨论】:

    猜你喜欢
    • 2011-06-03
    • 1970-01-01
    • 2021-08-14
    • 2020-02-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多