C++ 中快速、简单的 CSV 解析

Posted

技术标签:

【中文标题】C++ 中快速、简单的 CSV 解析【英文标题】:Fast, Simple CSV Parsing in C++ 【发布时间】:2012-05-30 10:20:39 【问题描述】:

我正在尝试解析一个简单的 CSV 文件,其数据格式如下:

20.5,20.5,20.5,0.794145,4.05286,0.792519,1
20.5,30.5,20.5,0.753669,3.91888,0.749897,1
20.5,40.5,20.5,0.701055,3.80348,0.695326,1

所以,一个非常简单且固定格式的文件。我将此数据的每一列存储到一个 STL 向量中。因此,我尝试使用标准库保持 C++ 方式,并且我在循环中的实现看起来像:

string field;
getline(file,line);
stringstream ssline(line);

getline( ssline, field, ',' );
stringstream fs1(field);
fs1 >> cent_x.at(n);

getline( ssline, field, ',' );
stringstream fs2(field);
fs2 >> cent_y.at(n);

getline( ssline, field, ',' );
stringstream fs3(field);
fs3 >> cent_z.at(n);

getline( ssline, field, ',' );
stringstream fs4(field);
fs4 >> u.at(n);

getline( ssline, field, ',' );
stringstream fs5(field);
fs5 >> v.at(n);

getline( ssline, field, ',' );
stringstream fs6(field);
fs6 >> w.at(n);

问题是,这非常慢(每个数据文件有超过 100 万行),在我看来有点不雅。是否有使用标准库的更快方法,或者我应该只使用 stdio 函数?在我看来,整个代码块将减少为一个 fscanf 调用。

提前致谢!

【问题讨论】:

重复以下问题:***.com/questions/1120140/csv-parser-in-c C CSV 解析器:sourceforge.net/projects/cccsvparser C CSV 编写器:sourceforge.net/projects/cccsvwriter 【参考方案1】:

当你可以用 7 个字符串流来做的时候,肯定不会有帮助。表现。 试试这个:

string line;
getline(file, line);

istringstream ss(line);  // note we use istringstream, we don't need the o part of stringstream

char c1, c2, c3, c4, c5;  // to eat the commas

ss >> cent_x.at(n) >> c1 >>
      cent_y.at(n) >> c2 >>
      cent_z.at(n) >> c3 >>
      u.at(n) >> c4 >>
      v.at(n) >> c5 >>
      w.at(n);

如果您知道文件中的行数,则可以在读取之前调整向量的大小,然后使用operator[] 而不是at()。这样可以避免边界检查,从而获得一点性能。

【讨论】:

完美!它工作得更好,更好。感谢您提供有关吃逗号的字符的提示! @KyleLynch:我强烈建议您检查 char 是否已初始化为逗号。此外,您应该检查流是否有效或设置异常标志,以防输出错误。 小事:一个字符吃逗号就足够了 输入字符串流需要包含哪些内容?为什么代码中没有提到?【参考方案2】:

我认为主要的瓶颈(撇开基于 getline() 的非缓冲 I/O 不谈)是字符串解析。由于您有“,”符号作为分隔符,您可以对字符串执行线性扫描并将所有“,”替换为“\0”(字符串结束标记,零终止符)。

类似这样的:

// tmp array for the line part values
double parts[MAX_PARTS];

while(getline(file, line))

    size_t len = line.length();
    size_t j;

    if(line.empty())  continue; 

    const char* last_start = &line[0];
    int num_parts = 0;

    while(j < len)
    
        if(line[j] == ',')
        
           line[j] = '\0';

           if(num_parts == MAX_PARTS)  break; 

           parts[num_parts] = atof(last_start);
           j++;
           num_parts++;
           last_start = &line[j];
        
        j++;
    

    /// do whatever you need with the parts[] array
 

【讨论】:

【参考方案3】:

我不知道这是否会比接受的答案更快,但我还是发布它以防你想尝试它。 您可以通过使用 fseek magic. 知道文件的大小,使用单个读取调用加载文件的全部内容,这将比多次读取调用快得多。

然后你可以做这样的事情来解析你的字符串:

//Delimited string to vector
vector<string> dstov(string& str, string delimiter)

  //Vector to populate
  vector<string> ret;
  //Current position in str
  size_t pos = 0;
  //While the the string from point pos contains the delimiter
  while(str.substr(pos).find(delimiter) != string::npos)
  
    //Insert the substring from pos to the start of the found delimiter to the vector
    ret.push_back(str.substr(pos, str.substr(pos).find(delimiter)));
    //Move the pos past this found section and the found delimiter so the search can continue
    pos += str.substr(pos).find(delimiter) + delimiter.size();
  
  //Push back the final element in str when str contains no more delimiters
  ret.push_back(str.substr(pos));
  return ret;


string rawfiledata;

//This call will parse the raw data into a vector containing lines of
//20.5,30.5,20.5,0.753669,3.91888,0.749897,1 by treating the newline
//as the delimiter
vector<string> lines = dstov(rawfiledata, "\n");

//You can then iterate over the lines and parse them into variables and do whatever you need with them.
for(size_t itr = 0; itr < lines.size(); ++itr)
  vector<string> line_variables = dstov(lines[itr], ",");

【讨论】:

以上是关于C++ 中快速、简单的 CSV 解析的主要内容,如果未能解决你的问题,请参考以下文章

OpenCSV快速方便导出CSV文件拿虾C++

什么是清理无法解析的 csv 文件的简单方法

C++ 中的 CSV 解析器不读取第一个元素

用 C++ 解析/编写 CSV 的首选库是啥? [关闭]

在 C++ 跨平台中解析 url 的简单方法?

python 用于解析详细的AWS计费CSV文件的简单脚本。脚本显示按区域,存储桶和我们分解的S3操作的成本