确定文件中字符串的长度

时间:2018-03-08 17:44:31

标签: c

考虑文件中的这3行:

This is the first line of a text.
Second line comes next.
File ends here.

我想读取这些行并将它们存储在一个数组中。问题是我不知道它们需要多长时间才能malloc所需的空间。

对于给出它们的长度非常小的例子,但是考虑到也有非常大的线。

我不想malloc 1000字节并将其定义为字符串的最大长度。那么有什么方法可以找出每一行的长度以便malloc适当的空间?

注意:我考虑过使用realloc,但是当字符串很长时,这不是一个糟糕的技术吗?

5 个答案:

答案 0 :(得分:3)

首先动态分配一些内存,它将表示文件中一行的平均长度。无论是什么 - 现在通过char阅读char - 然后当您到达malloced内存的末尾(malloc)时 - 使用reallocrealloc重新分配尺寸加倍)。然后在您找到\n后再次重新分配以释放您要求但不需要此行的额外内存。通过这种方式,您可以读取整行,然后您可以拥有存储每一行​​所需的内存(使用mallocrealloc)。

并回答你关于这是否足够好的评论 - 这里realloc的数量可以通过首先分配一个大块然后在你需要填充之后缩小它来大大减少。是的,这样做很多次都是性能密集型的,但我们每次都要加倍。因此,除非初始尺寸太小 - 否则它适合。

还有getline可以为你做难点但是这是POSIX的一部分所以使用它不会给你带来便携性。您可以查看那里提供的小示例,以了解如何使用它。

答案 1 :(得分:2)

从文件中读取未知长度的未知行数并且仅分配所需存储的标准方法是最初分配一些合理预期的指针数(使用指针指向 - -char ,例如双指针char **lines;),然后为每一行读取和分配,并按行顺序将保存行的内存地址分配给分配的指针,直到达到限制为止。你分配的指针数量,然后realloc指针的数量(通常是当前的两倍)并继续,根据需要重复。

虽然您可以使用fgets,但如果您有POSIX getline可用,它将使用其内部分配处理任何行的读取而不管其长度,使您的唯一工作就是分配副本该行并将该地址分配给您的下一个指针。 strdup使其成为一个快照,但如果没有,getline会返回它已读取的字符数(例如nchr = getline (&line, &n, fp);,使其成为char *buf = malloc (nchr + 1); strcpy (buf, line);中的strdup}的简单任务{ {1}}无效。

一个简短的例子,包括必要的验证:

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

#define NPTR 8

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

    size_t ndx = 0,             /* line index */
        nptrs = NPTR,           /* initial number of pointers */
        n = 0;                  /* line alloc size (0, getline decides) */
    ssize_t nchr = 0;           /* return (no. of chars read by getline) */
    char *line = NULL,          /* buffer to read each line */
        **lines = NULL;         /* pointer to pointer to each line */
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;

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

    /* allocate/validate initial 'nptrs' pointers */
    if (!(lines = calloc (nptrs, sizeof *lines))) {
        perror ("calloc-lines");
        return 1;
    }

    /* read each line with POSIX getline */
    while ((nchr = getline (&line, &n, fp)) != -1) {
        if (nchr && line[nchr - 1] == '\n') /* check trailing '\n' */
            line[--nchr] = 0;               /* overwrite with nul-char */
        char *buf = strdup (line);          /* allocate/copy line */
        if (!buf) {     /* strdup allocates, so validate */
            fprintf (stderr, "error: strdup allocation failed.\n");
            break;
        }
        lines[ndx++] = buf;     /* assign start address for buf to lines */
        if (ndx == nptrs) {     /* if pointer limit reached, realloc */
            /* always realloc to temporary pointer, to validate success */
            void *tmp = realloc (lines, sizeof *lines * nptrs * 2);
            if (!tmp) {         /* if realloc fails, bail with lines intact */
                perror ("realloc-lines");
                break;          /* don't exit, lines holds current lines */
            }
            lines = tmp;        /* assign reallocted block to lines */
            /* zero all new memory (optional) */
            memset (lines + nptrs, 0, nptrs * sizeof *lines);
            nptrs *= 2;         /* increment number of allocated pointers */
        }
    }
    free (line);                    /* free memory allocated by getline */

    if (fp != stdin) fclose (fp);   /* close file if not stdin */

    for (size_t i = 0; i < ndx; i++) {
        printf ("line[%3zu] : %s\n", i, lines[i]);
        free (lines[i]);            /* free memory for each line */
    }
    free (lines);                   /* free pointers */

    return 0;
}

示例输入文件

$ cat dat/3lines.txt
This is the first line of a text.
Second line comes next.
File ends here.

示例使用/输出

$ ./bin/getline_readfile <dat/3lines.txt
line[  0] : This is the first line of a text.
line[  1] : Second line comes next.
line[  2] : File ends here.

内存使用/错误检查

在你编写的动态分配内存的任何代码中,你有2个职责关于任何分配的内存块:(1)总是保留一个指向起始地址的指针内存块,(2)当不再需要时,它可以释放

必须使用内存错误检查程序,以确保您不会尝试访问内存或写入超出/超出已分配块的范围,尝试读取或基于未初始化值的条件跳转,最后,确认您释放了所有已分配的内存。

对于Linux valgrind是正常的选择。每个平台都有类似的记忆检查器。它们都很简单易用,只需通过它运行程序即可。

$ valgrind ./bin/getline_readfile <dat/3lines.txt
==12179== Memcheck, a memory error detector
==12179== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==12179== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==12179== Command: ./bin/getline_readfile
==12179==
line[  0] : This is the first line of a text.
line[  1] : Second line comes next.
line[  2] : File ends here.
==12179==
==12179== HEAP SUMMARY:
==12179==     in use at exit: 0 bytes in 0 blocks
==12179==   total heap usage: 5 allocs, 5 frees, 258 bytes allocated
==12179==
==12179== All heap blocks were freed -- no leaks are possible
==12179==
==12179== For counts of detected and suppressed errors, rerun with: -v
==12179== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

始终确认已释放已分配的所有内存并且没有内存错误。

答案 2 :(得分:0)

考虑到 1000字节的担心是1KB,系统RAM可能是GB的,并且由于磁盘和放大器,这是一种有趣的方式。无论如何,文件缓存一切都可能被加载到RAM中。 但是这里有一种方法可以浏览文件并查找将被视为行的最大字符数

    int Max_Line_Length_in_File ( FILE *fp )
    {
       char ch;
       int count = 0;
       int maxcount = -1;

       /* assumes fp is already opened and at beginning of text file */

       ch = fgets( fp );
       while ( ! feof( fp ) )
       {
          if (( ch == '\n' ) || ( ch == '\0' ))
             count = 0;
          else
             count++;

          if ( count > maxcount )
             maxcount = count;
       }
       /* don't forget to do a rewind on the fileptr if needed */
    }

一旦你知道一条线的最大长度,你可以做一个'malloc()'知道你需要的最小值......或者你可以为每一条线做和您可以轻松修改上述内容,并为找到的的数量添加一个计数器。因此,如果内存是一个问题,这里有一种方法可以使用少数几个变量来实现,通常为4个字节,因此少于16个字节可以获得number_of_lines和max_line_length的答案

答案 3 :(得分:0)

#include <stdio.h>  //Used for fopen, fseek, ftell, fread, fclose
#include <stdlib.h> //Used for malloc and free
#include <assert.h> //Used for assert

int main(void)
{
    FILE* file = fopen("your_file_here.txt", "r"); //Open a file
    fseek(file, 0, SEEK_END);                      //Find the end of the file
    long filesize = ftell(file);                   //Save the position (length)
    fseek(file, 0, SEEK_SET);                      //Return to the beginning of the file
    char* buffer = malloc(filesize + 1);           //Allocate enough memory
    assert(buffer);                                //Ensure that the memory was allocated
    fread(content, 1, filesize, file);             //Fill the allocated memory with the file content

    //Do whatever you like 
    //{...}

    free(buffer); //Free up memory
    fclose(file); //Close file
}

答案 4 :(得分:0)

支持它的系统(例如所有现代桌面操作系统)的简单解决方案是对整个文件进行内存映射。然后,文件内容可直接在内存中寻址,操作系统虚拟内存管理可为您处理内存和分页,无论文件大小如何。

然后,您可以直接对文件内容进行操作,就好像它是内存一样 - 没有必要进行显式分配,重新分配或(甚至应该回写) - 或者通过扫描换行获取每行开头和长度,分配完全所需的内存量并将其复制。

用于内存映射文件的Windows和POSIX API不同,但您可以找到适用于您所使用的任何系统的大量示例。