【问题标题】:How can I read and manipulate CSV file data in C++? [duplicate]如何在 C++ 中读取和操作 CSV 文件数据? [复制]
【发布时间】:2010-09-29 18:05:34
【问题描述】:

非常不言自明,我尝试了谷歌并获得了很多可怕的专家交流,我在这里也搜索过,但无济于事。最好是在线教程或示例。谢谢各位。

【问题讨论】:

  • 我不久前编写了 libcs​​v,它是一个小型且非常快速的 C CSV 解析器,也可以在 C++ 中使用。下载包含文档和示例程序。您可以通过sourceforge.net/projects/libcsv查看。
  • 罗伯特,如果您以 cmets 的身份给出答案,您如何期望获得更多的代表? :D
  • 也许他不需要更多的代表?
  • @ZamfirKerlukson 这是在那个问题之前大约 6 个月被问到的。
  • @Shog9 以什么方式重复?这是在其他问题之前提出的。

标签: c++ csv


【解决方案1】:

更多信息会很有用。

但最简单的形式:

#include <iostream>
#include <sstream>
#include <fstream>
#include <string>

int main()
{
    std::ifstream  data("plop.csv");

    std::string line;
    while(std::getline(data,line))
    {
        std::stringstream  lineStream(line);
        std::string        cell;
        while(std::getline(lineStream,cell,','))
        {
            // You have a cell!!!!
        }
    }
 }

另请参阅此问题:CSV parser in C++

【讨论】:

  • 是的,但这有什么好玩的? :p
  • 如果您允许在单元格中使用逗号,可能会通过引用单元格、转义逗号或两者兼而有之,会变得更加复杂。
  • 非常感谢。如果可以,我将如何从在线托管的 csv 获取数据?我是只做 data("csvhost.com/plop.csv") 还是有别的?
  • libCURL 是一个易于使用的 C 库,可以通过 HTTP(S) 获取远程文件。存在其他框架,例如 POCO(或者可能是 Boost 或 ACE 中的某些东西)。 C++ 标准 I/O 流不解决协议感知远程文件下载。
  • 漂亮的 STL 版本!当我要处理它需要调试的时候,我会小心翼翼地避免因愚蠢的文件 IO 的疯狂而使源代码混乱。
【解决方案2】:

您可以尝试使用 Boost Tokenizer 库,尤其是 Escaped List Separator

【讨论】:

  • 这是最好的方法。 escape_list_separator 正确处理边缘情况,例如带逗号的引号字符串。
  • 引用的字符串不是边缘情况(除非你有隧道视野)
【解决方案3】:

如果您真正要做的是操作 CSV 文件本身,那么 Nelson 的回答是有道理的。但是,我怀疑 CSV 只是您正在解决的问题的产物。在 C++ 中,这可能意味着您有这样的数据模型:

struct Customer {
    int id;
    std::string first_name;
    std::string last_name;
    struct {
        std::string street;
        std::string unit;
    } address;
    char state[2];
    int zip;
};

因此,当您处理数据集合时,使用std::vector&lt;Customer&gt;std::set&lt;Customer&gt; 是有意义的。

考虑到这一点,将您的 CSV 处理视为两个操作:

// if you wanted to go nuts, you could use a forward iterator concept for both of these
class CSVReader {
public:
    CSVReader(const std::string &inputFile);
    bool hasNextLine();
    void readNextLine(std::vector<std::string> &fields);
private:
    /* secrets */
};
class CSVWriter {
public:
    CSVWriter(const std::string &outputFile);
    void writeNextLine(const std::vector<std::string> &fields);
private:
    /* more secrets */
};
void readCustomers(CSVReader &reader, std::vector<Customer> &customers);
void writeCustomers(CSVWriter &writer, const std::vector<Customer> &customers);

一次读取和写入一行,而不是保留文件本身的完整内存表示。有一些明显的好处:

  1. 您的数据以一种对您的问题(客户)有意义的形式表示,而不是当前的解决方案(CSV 文件)。
  2. 您可以轻松地为其他数据格式添加适配器,例如批量 SQL 导入/导出、Excel/OO 电子表格文件,甚至是 HTML &lt;table&gt; 呈现。
  3. 您的内存占用可能更小(取决于相对 sizeof(Customer) 与单行中的字节数)。
  4. CSVReaderCSVWriter 可以作为内存模型(例如 Nelson 模型)的基础重用,而不会损失性能或功能。反之则不然。

【讨论】:

  • 提防引号。 CSV 有多种转义。如果您的字符串可以包含逗号,则将其引用。如果它被引用并包含双引号,那么你就有麻烦了。我相信 Excel 通过将引号加倍来转义引号,但我不确定。
【解决方案4】:

我曾经处理过很多 CSV 文件。我想补充一点建议:

1 - 根据来源(Excel 等),逗号或制表符可能嵌入到字段中。通常,规则是它们将受到“保护”,因为该字段将用双引号分隔,如“波士顿,MA 02346”。

2 - 某些来源不会用双引号分隔所有文本字段。其他来源会。其他人将分隔所有字段,甚至是数字。

3 - 包含双引号的字段通常会使嵌入的双引号加倍(并且字段本身用双引号分隔,如 "George ""Babe"" Ruth"。

4 - 一些源将嵌入 CR/LF(Excel 就是其中之一!)。有时它只是一个 CR。该字段通常会用双引号分隔,但这种情况很难处理。

【讨论】:

  • 如果你遵循这个,你应该没事 - tools.ietf.org/html/rfc4180
  • 换句话说,不存在“CSV格式”之类的东西,而是类似格式的一族。
【解决方案5】:

这对你自己来说是一个很好的练习:)

你应该把你的图书馆分成三个部分

  • 加载 CSV 文件
  • 在内存中表示文件,以便您可以修改和读取它
  • 将 CSV 文件保存回磁盘

所以您正在考虑编写一个包含以下内容的 CSVDocument 类:

  • 加载(const char* 文件);
  • 保存(const char* 文件);
  • GetBody

这样您就可以像这样使用您的库:

CSVDocument doc;
doc.Load("file.csv");
CSVDocumentBody* body = doc.GetBody();

CSVDocumentRow* header = body->GetRow(0);
for (int i = 0; i < header->GetFieldCount(); i++)
{
    CSVDocumentField* col = header->GetField(i);
    cout << col->GetText() << "\t";
}

for (int i = 1; i < body->GetRowCount(); i++) // i = 1 so we skip the header
{
    CSVDocumentRow* row = body->GetRow(i);
    for (int p = 0; p < row->GetFieldCount(); p++)
    {
        cout << row->GetField(p)->GetText() << "\t";
    }
    cout << "\n";
}

body->GetRecord(10)->SetText("hello world");

CSVDocumentRow* lastRow = body->AddRow();
lastRow->AddField()->SetText("Hey there");
lastRow->AddField()->SetText("Hey there column 2");

doc->Save("file.csv");

它为我们提供了以下接口:

class CSVDocument
{
public:
    void Load(const char* file);
    void Save(const char* file);

    CSVDocumentBody* GetBody();
};

class CSVDocumentBody
{
public:
    int GetRowCount();
    CSVDocumentRow* GetRow(int index);
    CSVDocumentRow* AddRow();
};

class CSVDocumentRow
{
public:
    int GetFieldCount();
    CSVDocumentField* GetField(int index);
    CSVDocumentField* AddField(int index);
};

class CSVDocumentField
{
public:
    const char* GetText();
    void GetText(const char* text);
};

现在你只需要从这里填写空白:)

相信我,当我这么说的时候,花时间学习如何制作库,尤其是那些处理数据加载、操作和保存的库,不仅会消除您对此类库存在的依赖,还会让您一个全能的更好的程序员。

:)

编辑

我不知道您对字符串操作和解析了解多少;因此,如果您遇到困难,我很乐意提供帮助。

【讨论】:

  • 老兄,过头了。谢谢百万吨。
  • 不,不要自己动手。使用经过良好测试的库。
【解决方案6】:

这里有一些你可以使用的代码。来自 csv 的数据存储在行数组中。每行都是一个字符串数组。希望这会有所帮助。

#include <iostream>
#include <string>
#include <fstream>
#include <sstream>
#include <vector>
typedef std::string String;
typedef std::vector<String> CSVRow;
typedef CSVRow::const_iterator CSVRowCI;
typedef std::vector<CSVRow> CSVDatabase;
typedef CSVDatabase::const_iterator CSVDatabaseCI;
void readCSV(std::istream &input, CSVDatabase &db);
void display(const CSVRow&);
void display(const CSVDatabase&);
int main(){
  std::fstream file("file.csv", std::ios::in);
  if(!file.is_open()){
    std::cout << "File not found!\n";
    return 1;
  }
  CSVDatabase db;
  readCSV(file, db);
  display(db);
}
void readCSV(std::istream &input, CSVDatabase &db){
  String csvLine;
  // read every line from the stream
  while( std::getline(input, csvLine) ){
    std::istringstream csvStream(csvLine);
    CSVRow csvRow;
    String csvCol;
    // read every element from the line that is seperated by commas
    // and put it into the vector or strings
    while( std::getline(csvStream, csvCol, ',') )
      csvRow.push_back(csvCol);
    db.push_back(csvRow);
  }
}
void display(const CSVRow& row){
  if(!row.size())
    return;
  CSVRowCI i=row.begin();
  std::cout<<*(i++);
  for(;i != row.end();++i)
    std::cout<<','<<*i;
}
void display(const CSVDatabase& db){
  if(!db.size())
    return;
  CSVDatabaseCI i=db.begin();
  for(; i != db.end(); ++i){
    display(*i);
    std::cout<<std::endl;
  }
}

【讨论】:

    【解决方案7】:

    使用 boost tokenizer 解析记录see here for more details.

    ifstream in(data.c_str());
    if (!in.is_open()) return 1;
    
    typedef tokenizer< escaped_list_separator<char> > Tokenizer;
    
    vector< string > vec;
    string line;
    
    while (getline(in,line))
    {
        Tokenizer tok(line);
        vec.assign(tok.begin(),tok.end());
    
        /// do something with the record
        if (vec.size() < 3) continue;
    
        copy(vec.begin(), vec.end(),
             ostream_iterator<string>(cout, "|"));
    
        cout << "\n----------------------" << endl;
    }
    

    【讨论】:

    【解决方案8】:

    看看 Kernighan & Pike 的“The Practice of Programming”(TPOP)。它包括一个在 C 和 C++ 中解析 CSV 文件的示例。但即使你不使用代码,这本书也值得一读。

    (上一个网址:http://cm.bell-labs.com/cm/cs/tpop/

    【讨论】:

    • 我目前无法访问这本书,但我不相信那里提供的解决方案是完整的或稳健的。
    • 这可能取决于您对完整和健壮的定义。它是只读的,也不能写。但 C++ 对我来说看起来不错 - 简单、健壮,适用于 Windows、旧 MacOS 和 Unix 的行尾(CRLF、CR 或 LF)。它没有所有的花里胡哨;它确实处理了嵌套引号等。代码在 URL 上在线。
    【解决方案9】:

    我发现了这个有趣的方法:

    CSV to C structure utility

    引用: CSVtoC 是一个将 CSV 或逗号分隔值文件作为输入并将其转储为 C 结构的程序。

    当然,您不能对 CSV 文件进行更改,但如果您只需要对数据进行内存中的只读访问,它就可以工作。

    【讨论】:

    • 链接不再起作用。
    猜你喜欢
    • 1970-01-01
    • 2013-06-09
    • 2021-12-10
    • 2017-12-11
    • 2012-01-19
    • 1970-01-01
    • 2013-09-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多