【问题标题】:Parse values from a text file in C [duplicate]从 C 中的文本文件解析值 [重复]
【发布时间】:2023-03-31 17:57:01
【问题描述】:

可能重复:
Parsing text in C

假设我以这种格式写入了一个文本文件:

key1/value1
key2/value2
akey/withavalue
anotherkey/withanothervalue

我有一个像这样的链表:

struct Node
{
    char *key;
    char *value;
    struct Node *next;
};

保存值。我将如何读取 key1 和 value1?我正在考虑将逐行放入缓冲区并使用 strtok(buffer, '/')。那行得通吗?还有哪些其他方法可以奏效,可能更快或更不容易出错?如果可以,请提供代码示例!

【问题讨论】:

  • 对不起,我的错。继续并关闭它。
  • 我还会查看 .ini 文件格式和 Java .properties 文件。这将使您能够找到可以执行此操作的库,并且还具有其他语言可以读取的更标准格式。这不会是额外的工作

标签: c file-io text-files token


【解决方案1】:

由于您的问题非常适合优化内存碎片,因此这里有一个实现,它使用一些简单的神秘魔法将所有字符串和结构本身分配到一块内存中。

销毁节点时,您只需调用一次free() 即可调用节点本身。

struct Node *list = NULL, **nextp = &list;
char buffer[1024];

while (fgets(buffer, sizeof buffer, file) != NULL) {
    struct Node *node;

    node = malloc(sizeof(struct Node) + strlen(buffer) + 1);
    node->key = strtok(strcpy((char*)(node+1), buffer), "/\r\n");
    node->value = strtok(NULL, "\r\n");
    node->next = NULL;
    *nextp = node;
    nextp = &node->next;
}

解释:

有 20 个 cmets 和一个无法解释的反对票,我认为代码需要一些解释,特别是关于所采用的技巧:

  1. 建立一个链表:

    struct Node *list = NULL, **nextp = &list;
    ...
    *nextp = node;
    nextp = &node->next;
    

    这是一种以正向迭代创建链表的技巧,而不必对链表的头部进行特殊处理。它使用指向下一个节点的指针。首先nextp指针指向链表头指针;在第一次迭代中,通过这个指向指针的指针设置列表头,然后将nextp 移动到该节点的下一个指针。后续迭代填充最后一个节点的next指针。

  2. 单次分配:

    node = malloc(sizeof(struct Node) + strlen(buffer) + 1);
    node->key = ... strcpy((char*)(node+1), buffer) ...
    

    我们必须处理三个指针:节点本身、键字符串和值字符串。这通常需要三个单独的分配(malloc、calloc、strdup...),因此需要释放单独的版本(免费)。相反,在这种情况下,树元素的空间在sizeof(struct Node) + strlen(buffer) + 1 中求和并传递给单个malloc 调用,该调用返回单个内存块。这块内存的开头分配给node,即结构本身。额外的内存 (strlen(buffer)+1) 紧跟在节点之后,它的地址是使用 node+1 使用指针算法获得的。它用于复制从文件中读取的整个字符串(“key/value\n”)。

    由于malloc 对每个节点调用一次,因此进行了一次分配。这意味着您不需要调用free(node->key)free(node->value)。事实上,它根本行不通。只需一个 free(node) 即可将结构和两个字符串释放到一个块中。

  3. 行解析:

    node->key = strtok(strcpy((char*)(node+1), buffer), "/\r\n");
    node->value = strtok(NULL, "\r\n");
    

    strtok 的第一次调用将指针返回到缓冲区本身的开头。它会查找 '/'(另外还用于行尾标记)并用 NUL 字符打破字符串。因此,“key/value\n”在“key”和“value\n”之间被中断,中间有一个 NUL 字符,并返回指向第一个的指针并存储在node->key 中。对strtok 的第二次调用将处理剩余的“value\n”,去除行尾标记并返回一个指向“value”的指针,该指针存储在node->value 中。

我希望这可以解决有关上述解决方案的所有问题......对于一个封闭的问题来说太过分了。 The complete test code is here.

【讨论】:

  • 我觉得这很好。但是有一个错误:如果文件的最后一行没有'\n',则不会找到令牌并且 strtok 返回 NULL。因此,程序将无法添加最终的合法值部分。
  • strtok 将通过两次调用解析最终值(/ 的右侧)部分。不需要第二个\n 令牌。只需使用相同的/ 令牌调用 strtok 两次,第二次调用为 NULL ptr。错误修复...
  • 另外:如果你想扩展内存,malloc 不应该是calloc 吗?您无法保证调用malloc...之间的连续内存...
  • @drewk:即使最后一行没有'\n',它也应该工作,它只会考虑文件的其余部分,直到 EOF 作为最后一个标记。 strtok 只会在前一次调用中未找到“/”时返回 NULL。 OP 没有说第二部分没有任何“/”。 strtok'ing a '\n' 的好处是它会为我们删除换行符。
  • @drewk:calloc()malloc() 的区别在于前者用零清理内存。对任何这些函数的一次调用将返回一个连续的内存块,这是我所期望的。我不关心不同调用之间的连续记忆。没有标准的 C 函数可以保证这一点。
【解决方案2】:

您也可以使用fscanf 将输入行直接解析为键和值:

char key[80], value[80];
fscanf (pFile, "%s/%s", key, value);

但是,这种方法的缺点是您需要提前为键和值分配足够大的缓冲区(或使用临时缓冲区,然后将其值复制到其最终目的地,并分配正确的大小)。使用strtok,您可以检查每个键和值的长度,然后分配一个大小正好合适的缓冲区来存储它。

更新:正如评论者所指出的,fscanf 的另一个(可能更严重的)缺点是它不适用于包含空格的字符串。

【讨论】:

  • 如果键/值是通用字符串,你将如何实现?
  • @Let_Me_Be,“通用字符串”是什么意思?
  • @Péter:可能包含空格的字符串。 scanf 的 %s 格式匹配一系列非空白字符。
  • @Juliano,嗯...好点,我还没考虑过。
  • 应该没问题,因为输入也不会有任何空格。我一定会适当地分配结构。那么上面的代码应该能做到吗?
【解决方案3】:

循环换行:
1) 找到'/'
2) 在“/”位置分配对键/值
3) 填充键和值
在代码中:

字符* p; if(p = strchr(str,'/') { *p++ = 0; 键 = strdup(str); 值 = strdup(p); }

【讨论】:

  • 此代码不起作用
【解决方案4】:

如果您不介意将键和值都放在一个内存块(两个字符串)中,您可以读入缓冲区,找到“/”将其更改为“\0”并指向值指针就在 '\0' 字符后面。只是不要忘记只在键值上调用 free() (这将释放键和值)。

【讨论】:

  • 我需要不同字符串中的键和值。但是使用空终止符 '\0' 是个好主意
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-01-15
  • 1970-01-01
  • 1970-01-01
  • 2010-09-07
  • 2017-07-04
  • 1970-01-01
相关资源
最近更新 更多