使用strtok()解析CSV文件时,如何将文件指针重置至文件开头?
CSV文件解析:重置文件指针与方案优化
一、无需关闭重开重置文件指针的方法
完全可以不用关闭再打开文件,通过标准C库的fseek()或rewind()函数就能将文件指针重置到文件开头:
rewind(fp):直接将文件指针定位到文件起始位置,等价于fseek(fp, 0L, SEEK_SET),用法更简洁。fseek(fp, 0, SEEK_SET):更灵活,可定位到文件任意位置,这里SEEK_SET表示从文件开头偏移0字节。
注意:这两个函数仅对可随机访问的文件有效(比如本地磁盘文件),管道、套接字这类流式输入无法使用。
另外纠正一个误解:strtok()操作的是内存中buffer里的字符串,并不会移动文件指针,文件指针是被fgets()逐步移动的。所以重置文件指针只需要操作FILE*对象,和strtok()的状态无关。
二、strtok()的替代优化方案
strtok()有两个明显缺陷:线程不安全(依赖全局静态变量)、无法处理CSV中字段包含分隔符(比如被引号包裹的"abc;def")的场景。推荐以下替代方案:
strtok_r():strtok()的线程安全版本,需要传入一个指针保存分割状态,用法示例:char *saveptr; char *value = strtok_r(buffer, &csv_separator, &saveptr); while (value) { // 处理字段 value = strtok_r(NULL, &csv_separator, &saveptr); }strsep():比strtok更灵活,会修改传入的字符串指针,适合处理分割字段,用法:char *token; char *buf_copy = strdup(buffer); // strsep会修改原字符串,需先复制 token = strsep(&buf_copy, &csv_separator); while (token) { // 处理字段 token = strsep(&buf_copy, &csv_separator); } free(buf_copy);- 专用CSV解析库:如果需要处理复杂CSV(含引号、换行符的字段),可以使用成熟的C语言CSV库,比如libcsv,能覆盖所有CSV标准场景。
三、现有代码的问题与修正
你的代码存在几个需要调整的地方:
get_file_height()中不应关闭文件:函数内部调用fclose(fp)会导致调用者的文件指针失效,文件的打开和关闭应由调用者负责。- 重复的列格式化代码:
print_entire_file()中多次判断column == 0/1/2可以简化为直接输出固定制表符。 - 缺少文件指针重置:多次调用解析函数前需要手动重置文件指针,避免之前的遍历影响结果。
修正后的核心代码示例(以get_file_height()为例):
struct s_df_dim get_file_height(FILE *fp) { struct s_df_dim df_dim = {0, 0}; char buffer[BUFFLEN]; int row = 0; int column = 0; // 重置指针到开头,避免之前的操作干扰 rewind(fp); while (fgets(buffer, BUFFLEN, fp)) { column = 0; row++; if (row == 1) continue; char *saveptr; char *value = strtok_r(buffer, &csv_separator, &saveptr); while (value) { value = strtok_r(NULL, &csv_separator, &saveptr); column++; } // 确保列数取最大值(防止行长度不一致) if (column > df_dim.ncol) { df_dim.ncol = column; } } df_dim.nrow = row; // 重置指针,方便后续操作 rewind(fp); return df_dim; }
四、完整代码与测试文件
database.c
#include <stdlib.h> #include <stdio.h> #include "main.h" #define BUFFLEN 1024 void print_entire_file(FILE *fp) { char buffer[BUFFLEN]; int row = 0; int column = 0; rewind(fp); while (fgets(buffer, BUFFLEN, fp)) { column = 0; row++; char *saveptr; char *value = strtok_r(buffer, &csv_separator, &saveptr); while (value) { printf("\t%s", value); value = strtok_r(NULL, &csv_separator, &saveptr); column++; } printf("\n"); } } struct s_df_dim get_file_height(FILE *fp) { struct s_df_dim df_dim = {0, 0}; char buffer[BUFFLEN]; int row = 0; int column = 0; rewind(fp); while (fgets(buffer, BUFFLEN, fp)) { column = 0; row++; if (row == 1) continue; char *saveptr; char *value = strtok_r(buffer, &csv_separator, &saveptr); while (value) { value = strtok_r(NULL, &csv_separator, &saveptr); column++; } if (column > df_dim.ncol) { df_dim.ncol = column; } } df_dim.nrow = row; rewind(fp); return df_dim; } void print_cell(FILE *fp, int colid, int rowid) { char buffer[BUFFLEN]; int row = 0; int column = 0; rewind(fp); while (fgets(buffer, BUFFLEN, fp)) { column = 0; row++; char *saveptr; char *value = strtok_r(buffer, &csv_separator, &saveptr); while (value) { if ((row == rowid) && (column == colid)) { printf("Cell (%d,%d): %s\n", rowid, colid, value); return; // 找到目标单元格后直接返回 } value = strtok_r(NULL, &csv_separator, &saveptr); column++; } } printf("Cell (%d,%d) not found\n", rowid, colid); } // 补充main函数示例 int main() { FILE *fp = fopen("file.csv", "r"); if (!fp) { perror("Failed to open file"); return 1; } // 获取文件维度 struct s_df_dim dim = get_file_height(fp); printf("Rows: %d, Columns: %d\n", dim.nrow, dim.ncol); // 打印整个文件 printf("\nEntire file:\n"); print_entire_file(fp); // 打印指定单元格 printf("\nPrint cell (2,1):\n"); print_cell(fp, 1, 2); fclose(fp); return 0; }
main.h
#ifndef MAIN_H #define MAIN_H char csv_separator = ';'; // 可切换为','或';' struct s_df_dim { int nrow; int ncol; }; #endif // MAIN_H
Makefile
CC = gcc CFLAGS = -g -Wall BINC=database allc: $(BINC) $(BINC): $(BINC).c *.h $(CC) $(CFLAGS) $< -o $@ clean: $(RM) -rf $(BINC) *.dSYM runc: allc ./$(BINC)
file.csv
asdf;8234;a sdfasg;3479;z rasdb;10984;d adwekrj;23460;c
内容的提问来源于stack exchange,提问作者ecjb
相关产品推荐
相关产品推荐

