You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C语言读取CSV至结构体数组异常及后续功能选型咨询

CSV解析异常及结构体数组适用性问题

一、CSV解析异常排查与修复

问题现象

尝试读取含3940条数据的CSV文件,使用strtok编写解析代码后,仅读取到1010条数据,且读取起始行为文件的第2931行,而非开头。

代码实现

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

typedef struct {
    char location[100];
    char city[100];
    int price;
    int room;
    int bathroom;
    int carpark;
    char type[100];
    char furnish[100];
} data;

int main() {

    data house[4000];

    FILE *file = fopen("data.csv", "r");
    char buffer[1000];
    fgets(buffer, 1000, file);
    int n = 0; 
    while (fgets(buffer, 1000, file)) {
        char *token = strtok(buffer, ",");
        strcpy(house[n].location, token);

        token = strtok(NULL, ",");
        strcpy(house[n].city, token);

        token = strtok(NULL, ",");
        house[n].price = atoi(token);

        token = strtok(NULL, ",");
        house[n].room = atoi(token);

        token = strtok(NULL, ",");
        house[n].bathroom = atoi(token);

        token = strtok(NULL, ",");
        house[n].carpark = atoi(token);

        token = strtok(NULL, ",");
        strcpy(house[n].type, token);

        token = strtok(NULL, "\n");
        strcpy(house[n].furnish, token);

        n++;
    }

    fclose(file);

    for (int i = 0; i < n; i++) {
         printf("%s,%s,%d,%d,%d,%d,%s,%s\n",
                house[i].location, house[i].city, house[i].price,
                house[i].room, house[i].bathroom, house[i].carpark,
                house[i].type, house[i].furnish);
    }

    return 0;
}

问题原因分析

  1. 长行截断导致解析错位:fgets(buffer, 1000, file)的缓冲区大小为1000,若CSV中某行数据长度超过1000,fgets会截断该行,剩余内容会被当作下一行处理,导致后续所有行的字段解析完全错位,最终触发未定义行为提前终止循环。
  2. 未处理带引号的CSV字段:标准CSV中,字段若包含逗号、换行符等特殊字符会用引号包裹,strtok无法识别这种格式,会将引号内的逗号当作分隔符,破坏字段完整性。
  3. 空指针访问风险:未检查strtok返回的token是否为NULL,若某行字段数量不足,strcpy或atoi会访问空指针,直接终止程序。
  4. 最后字段的换行处理不当:用"\n"作为最后一个字段的分隔符,若行尾存在多余空格或字段本身包含换行(带引号场景),会导致字段内容错误。

修复方案

  1. 增加文件打开检查:避免文件打开失败后继续执行后续操作:
    FILE *file = fopen("data.csv", "r");
    if (!file) {
        perror("Failed to open file");
        return 1;
    }
    
  2. 处理长行与带引号字段:
    • 增大缓冲区至合理大小(如4096),或实现动态读取逻辑;
    • 替换strtok为支持标准CSV格式的解析逻辑,例如手动处理引号包裹的字段:遇到引号时,跳过直到下一个引号,再寻找分隔符。
  3. 检查token有效性:每次调用strtok后判断返回值,避免空指针访问:
    char *token = strtok(buffer, ",");
    if (!token) break;
    strcpy(house[n].location, token);
    // 后续每个token都需添加类似检查
    
  4. 修正最后字段的换行处理:用",\n"作为分隔符,或手动去除字段末尾的换行符:
    token = strtok(NULL, ",\n");
    if (!token) break;
    // 手动去除换行(可选)
    size_t len = strlen(token);
    if (len > 0 && token[len-1] == '\n') token[len-1] = '\0';
    strcpy(house[n].furnish, token);
    

二、结构体数组的适用性及替代方案

当前方案的适用性

结构体数组完全适合实现显示、搜索、排序、导出这些功能:

  • 显示:直接遍历数组输出即可;
  • 搜索:遍历数组匹配目标条件;
  • 排序:使用标准库qsort函数,自定义比较函数,4000条数据的规模下效率足够;
  • 导出:遍历数组将数据写入文件。

优化替代方案

如果未来数据量大幅增长(如十万条以上),建议使用动态分配数组替代栈上固定大小数组,避免栈溢出:

// 初始分配4000个结构体的内存
data *house = malloc(4000 * sizeof(data));
if (!house) {
    perror("Failed to allocate memory");
    fclose(file);
    return 1;
}
// 后续需要扩容时使用realloc
size_t current_size = 4000;
if (n >= current_size) {
    current_size *= 2;
    house = realloc(house, current_size * sizeof(data));
    if (!house) {
        perror("Failed to reallocate memory");
        free(house);
        fclose(file);
        return 1;
    }
}

若需要频繁执行插入、删除操作,链表结构会更灵活,但你的需求中排序、搜索依赖随机访问特性,数组的效率远高于链表,因此结构体数组仍是最优选择。

内容的提问来源于stack exchange,提问作者Zain Firdaus

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 13:55:54