C将文件逐行读入字符串数组并排序

时间:2016-04-06 15:43:05

标签: c arrays string file

所以我想创建一个基本的C应用程序mysort,它接受​​一个文件列表,逐行读取每个文件到缓冲区并按字母顺序对行进行排序。代码看起来像这样(加上参数解析等):

//How do I initialize an array of 1024byte-Strings with an unknown amount of fields?
char** lines; 
int lineNum = 0;

for(int num_files = j; num_files < argc; num_files++){ //iterate through all files
  FILE * filepointer ;
  char * line = NULL;
  size_t len = 0;
  ssize_t read;

  filepointer = fopen(argv[num_files], "r");    
  if (filepointer == NULL)
    exit(EXIT_FAILURE);

  //TODO: write each line into a new spot of the array, this try doesn't work!

  while ((read = getline(&line, &len, filepointer)) != -1) { 
    //the lines may be assumed to be a max of 1024 bytes
    lines[lineNum] = malloc(1024 * sizeof(char)); 
    //lines[lineNum] = line;
    strcpy(lines[lineNum], line);
    lineNum++;
  }

  fclose(fp);
  if (line)
    free(line);

  //These values might be wrong, but that isn't the issue I'm adressing
  //just for illustration
  qsort(lines , argc - 1, sizeof(char *), cmpstringp) 

  //do something with the sorted lines
}

由于我必须使用qsort(3),我需要在某个时刻生成一个char**来保存所有行。

完成这项任务的好方法是什么?我是否需要自己的数据结构才能动态存储多个相同的对象?

此处未初始化lines char **数组,因此该程序无效。但由于在程序开始时行数完全未知,因此可能没有明确定义(除非你知道一个聪明的函数来解决这个问题)

到目前为止,我想出的唯一方法是定义我自己的动态数据结构(例如LinkedList)或解析所有文件两次以确定将生成的行数。

对我来说,两者似乎都非常优雅,但也许我只是不习惯C代码。

2 个答案:

答案 0 :(得分:2)

我看到解决问题的两种方式:

1) 浏览文件,计算新行字符的数量(并将其保存到 nl_count ),然后您可以分配这样的行。

int nl_count = 0;
int c;

while ((c = fgetc(fp)) != EOF)
   if (c == '\n')
      nl_count++;
...
lines = malloc(nl_count * sizeof(char *));


这样你就必须在 cmpstringp 函数中覆盖一些特殊情况,因为你可能会得到一些只包含&#39; \ n&#39;的行。
编辑 1.实际上,在任何一种情况下,您都必须检查此特殊情况。)
编辑 2.您可以通过一个错误,因为最后一行不必以&#39; \ n&#39;结束。)

2) 为行设置一些基本大小,并在读取的实际行数达到此基本大小时重新分配更多空间。

#define BASE_SIZE 32
#define GROW_STEP 2

int size;

size = BASE_SIZE
lines = malloc(size * sizeof(char *));

lines_read = 0;
while ((read = getline(&line, &len, fp)) != -1) { 
   lines_read++;
   if (lines_read > size) {
       size *= GROW_STEP;
       lines = realloc (lines, size * sizeof (char *));
   }
   lines[lineNum] = strdup(line);
   lineNum++;
}

请注意,在最坏的情况下,您将分配两倍于实际需要的空间。
另外,如果你使用strdup(),你应该释放分配的内存。

...
for (i = 0; i < lines_read; i++)
    free(lines[i]);

答案 1 :(得分:1)

//How do I initialize an array of 1024byte-Strings with an unknown amount of fields?

显然,你没有。如果你初始化某些东西,那么你就知道那件东西的所有细节。

我想你问的是如何为未知数量的字符串指针保留内存,但是你不这样做。此外,请注意,对于像您建议的char *数组,不需要1024字节限制;只有当您打算将数据结构化为char的2D数组时才有意义。读完字符串后,知道需要多少空间,例如,我观察到这段代码......

    //the lines may be assumed to be a max of 1024 bytes
    lines[lineNum] = malloc(1024 * sizeof(char)); 
    //lines[lineNum] = line;
    strcpy(lines[lineNum], line);

...如果写成:

,则既简单又没有固有的大小限制
    lines[linenum] = strdup(line);

事实上,如果您的行平均少于1023个字符,那么也会占用更少的空间。

关于整个阵列的空间,你可以做的是随时随地保留内存。这可能意味着最初malloc()空间用于多个字符串,并realloc()在需要时获得更多空间。它也可能意味着最初将字符串读入单个字符串或固定大小的字符串数组的链接列表,然后在知道有多少字符串后构建单片数组。

链接列表替代瞬态需要两倍的字符串指针存储空间,但这并不算太糟糕,因为字符串内容不需要重复。相对于malloc() / realloc()方法的一些天真实现,这具有相对较低的内存分配成本的优势。

因为重新分配通常需要将所有数据(在本例中为指针)从一个块复制到一个新的更大的数据块,所以通常需要限制重新分配的数量。在像你这样的情况下,通常的策略是在几何上而不是线性地增加分配大小。也就是说,每当您发现需要更多空间时,您就会分配足够的新空间,比如说,已经有两倍的字符串。其总成本在数据量上呈线性增长。虽然事实证明你只需要一个的空间,但它仍然不需要比链接列表更多的空间+动态数组转换所需的空间。