24、CSV文件解析实战:读取并解析逗号分隔值文件
CSV文件,说白了就是逗号分隔值文件。你想想看,Excel表格导出来,或者数据库导出来,最常见的就是这种格式。我刚开始学C语言那会儿,觉得这玩意儿简单得很——不就是按逗号切分字符串嘛。结果真上手做项目,才发现坑多着呢。
今天咱们就手把手把这个CSV解析给啃下来。我会把我在实际项目中踩过的坑、总结的经验都抖出来。
CSV文件长什么样?
先看一个典型的CSV文件内容:
姓名,年龄,城市,职业
张三,28,北京,工程师
李四,35,上海,设计师
王五,22,广州,学生
嗯,就是这么简单。每行是一条记录,逗号是字段分隔符。但实际项目中,CSV可没这么乖。我遇到过的情况包括:
- 字段里包含逗号(比如地址"北京市,朝阳区")
- 字段里包含换行符
- 字段用双引号包裹
- 空字段(比如"张三,,北京")
所以,一个健壮的CSV解析器,必须能处理这些情况。
解析的核心思路
我个人习惯把CSV解析分成三个步骤:
- 按行读取——用fgets或fscanf逐行读
- 按逗号切分——遍历每个字符,遇到逗号就切一刀
- 处理特殊字符——双引号、转义符等
说白了,就是状态机。每读一个字符,判断当前是在普通字段里,还是在双引号字段里,还是在等待下一个字段。
核心原则:不要用strtok!不要用strtok!不要用strtok!
strtok会修改原字符串,而且不能处理空字段。我曾经在项目里吃过这个亏,后来老老实实自己写解析器。
代码实现:一个健壮的CSV解析器
下面这个函数,是我在实际项目中打磨过的版本。它能处理双引号、空字段、字段内逗号等情况。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_LINE 1024
#define MAX_FIELDS 100
// 解析一行CSV,返回字段个数
int parse_csv_line(char *line, char *fields[], int max_fields) {
int count = 0;
char *p = line;
char *start = line;
int in_quotes = 0;
while (*p) {
if (*p == '"') {
in_quotes = !in_quotes; // 切换引号状态
} else if (*p == ',' && !in_quotes) {
// 遇到逗号且不在引号内,切分字段
*p = '\0';
fields[count] = start;
count++;
start = p + 1;
if (count >= max_fields) break;
}
p++;
}
// 处理最后一个字段
fields[count] = start;
count++;
// 去掉字段首尾的双引号
for (int i = 0; i < count; i++) {
char *f = fields[i];
int len = strlen(f);
if (len >= 2 && f[0] == '"' && f[len-1] == '"') {
f[len-1] = '\0';
fields[i] = f + 1;
}
}
return count;
}
int main() {
FILE *fp = fopen("data.csv", "r");
if (!fp) {
perror("打开文件失败");
return 1;
}
char line[MAX_LINE];
char *fields[MAX_FIELDS];
int line_num = 0;
while (fgets(line, sizeof(line), fp)) {
// 去掉换行符
line[strcspn(line, "\n")] = '\0';
line_num++;
int count = parse_csv_line(line, fields, MAX_FIELDS);
printf("第%d行: %d个字段\n", line_num, count);
for (int i = 0; i < count; i++) {
printf(" 字段%d: %s\n", i+1, fields[i]);
}
}
fclose(fp);
return 0;
}
小技巧:如果CSV文件很大(比如几百万行),建议用mmap或者分块读取,别一次性读到内存里。我之前处理一个2GB的CSV,差点把服务器搞崩了。
处理更复杂的情况
上面的代码已经能应付大部分场景了。但如果你遇到下面这些情况,还得再加强:
| 场景 | 处理方法 |
|---|---|
| 字段内包含双引号(如"他说""你好""") | 连续两个双引号转义为一个 |
| 字段内包含换行符 | 需要跨行读取,直到找到匹配的引号 |
| 编码问题(UTF-8 BOM头) | 读取第一行时检查前三个字节是否为EF BB BF |
| 空行 | 跳过或返回空字段数组 |
我曾经在处理一个从Windows导出的CSV时,发现每行末尾都有\r\n,而Linux下只有\n。这个坑让我排查了半天。所以,跨平台解析时,一定要处理\r字符。
知识体系总览
下面这张图,把CSV解析的核心逻辑串起来了:
避坑指南
我曾经犯过的错:
- 直接用fscanf("%s")读CSV——遇到空格就停了,字段根本读不全
- 用strtok切分——空字段直接跳过,数据对不齐
- 没处理BOM头——第一行第一个字段前面多了三个不可见字符
- 假设每行末尾都是\n——Windows的\r\n导致最后一个字段多了\r
嗯,这些坑我都踩过。现在写CSV解析器,我会先问清楚:文件是哪个系统生成的?字段里有没有特殊字符?要不要处理引号?
总结一下
CSV解析看着简单,但要做好、做稳,还是得下点功夫。核心就三点:
- 逐字符解析,别偷懒用库函数
- 状态机思维,处理引号和转义
- 边界情况,空字段、空行、特殊字符都要考虑
你想想看,如果连CSV都解析不好,后面那些数据分析、报表生成的工作根本没法做。所以,把这个基础打扎实了,后面就顺了。