在C中实现可变字符串的最有效方法是什么?

时间:2013-10-12 19:08:12

标签: c string

我目前正在C中实现一个非常简单的 JSON解析器,我希望能够使用可变字符串(我可以在没有可变字符串的情况下实现它,但我想学习最好的无论如何,这样做的方式)。我目前的方法如下:

char * str = calloc(0, sizeof(char));
//The following is executed in a loop
int length = strlen(str);
str = realloc(str, sizeof(char) * (length + 2));
//I had to reallocate with +2 in order to ensure I still had a zero value at the end
str[length] = newChar;
str[length + 1] = 0;

我对这种方法很满意,但是由于我总是每次只附加一个字符(并且为了争论,我没有做任何事情),这让我觉得有点低效前瞻以找到我的字符串的最终长度)。另一种方法是使用链表:

struct linked_string
{
    char character;
    struct linked_string * next;
}

然后,一旦我完成处理,我就可以找到长度,分配一个适当长度的char *,然后遍历链表创建我的字符串。

然而,这种方法似乎内存效率低下,因为我必须为每个字符和指向下一个字符的指针分配内存。因此,我的问题是双重的:

  • 创建链表然后再创建C字符串比每次重新分配C字符串更快吗?
  • 如果是这样,获得的速度是否值得更大的内存开销?

4 个答案:

答案 0 :(得分:4)

无论是否存储char或其他内容,动态数组的标准方法是在增长时双倍容量。 (从技术上讲,任何多个工作,但加倍很容易,并在速度和内存之间取得良好的平衡。)你还应该抛弃0终止符(如果你需要返回一个0终止的字符串,最后添加一个)并跟踪已分配的大小(也称为容量)和实际存储的字符数。否则,由于重复使用strlenShlemiel the painter's algorithm),您的循环具有二次时间复杂度。

通过这些变化,时间复杂度是线性的(每次追加操作的摊销常数时间),实际表现对于各种丑陋的低级别原因都非常好。

理论上的缺点是你使用的内存是严格必要的两倍,但链表需要至少五倍于相同数量字符的内存(64位指针,填充和典型的malloc开销,更像是24或32次)。这在实践中通常不是问题。

答案 1 :(得分:1)

不,链接列表肯定不是“更快”(无论你如何衡量这样的事情)。这是一个可怕的开销。

如果您确实发现当前的方法是瓶颈,您可以随时以2的大小分配或重新分配字符串。然后,当您越过realloc数组的总大小的边界时,您只需char

答案 2 :(得分:1)

我建议将整个文本集读入一个内存分配是合理的,然后通过NUL终止每个字符串。然后计算字符串的数量,并为每个字符串创建一个指针数组。这样你就可以为文本区域分配一个内存,为指针数组分配一个内存。

答案 3 :(得分:0)

可变长度数组/字符串/大多数具有大小容量的实现。容量是分配的大小,大小是实际使用的大小。

struct mutable_string {
   char* data;
   size_t capacity;
};

分配新字符串如下所示:

#define INITIAL_CAPACITY 10

mutable_string* mutable_string_create_empty() {
   mutable_string* str = malloc(sizeof(mutable_string));
   if (!str) return NULL;

   str->data = calloc(INITIAL_CAPACITY, 1);
   if (!str->data) { free(str); return NULL; }

   str->capacity = INITIAL_CAPACITY;

   return str;
}

现在,只要你需要在字符串中添加一个字符,就可以这样做:

int mutable_string_concat_char(mutable_string* str, char chr) {
   size_t len = strlen(str->data);
   if (len < str->capacity) {
      str->data[len] = chr;
      return 1; //Success
   }

   size_t new_capacity = str->capacity * 2;
   char* new_data = realloc(str->data, new_capacity);
   if (!new_data) return 0;

   str->data = new_data;
   str->data[len] = chr;
   str->data[len + 1] = '\0';
   str->capacity = new_capacity;
}

链表方法更糟糕,因为:

  1. 每次添加角色时,您仍需要进行分配调用;
  2. 消耗 LOT 更多内存。此方法最多消耗sizeof(size_t) + (string_length + 1) * 2。该方法消耗string_length * sizeof(linked_string)
  3. 通常,链接列表比数组的缓存更少。