如何在C++中读取和解析CSV文件?

Use*_*er1 246 c++ csv parsing text

我需要在C++中加载和使用CSV文件数据.此时它实际上只是一个以逗号分隔的解析器(即不用担心转义新行和逗号).主要需求是逐行解析器,每次调用方法时都会返回下一行的向量.

我发现这篇文章很有前途:http: //www.boost.org/doc/libs/1_35_0/libs/spirit/example/fundamental/list_parser.cpp

我从未使用过Boost的精神,但我愿意尝试.但只有在没有更直接的解决方案的情况下,我才会忽视.

Mar*_*ork 276

如果你不关心转义逗号和换行符,
你不能在引号中嵌入逗号和换行符(如果你不能逃避那么......)
那么它只有大约三行代码(OK 14 - >但它的只读15整个文件).

std::vector<std::string> getNextLineAndSplitIntoTokens(std::istream& str)
{
    std::vector<std::string>   result;
    std::string                line;
    std::getline(str,line);

    std::stringstream          lineStream(line);
    std::string                cell;

    while(std::getline(lineStream,cell, ','))
    {
        result.push_back(cell);
    }
    // This checks for a trailing comma with no data after it.
    if (!lineStream && cell.empty())
    {
        // If there was a trailing comma then add an empty element.
        result.push_back("");
    }
    return result;
}
Run Code Online (Sandbox Code Playgroud)

我只想创建一个代表一行的类.
然后流入该对象:

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

class CSVRow
{
    public:
        std::string const& operator[](std::size_t index) const
        {
            return m_data[index];
        }
        std::size_t size() const
        {
            return m_data.size();
        }
        void readNextRow(std::istream& str)
        {
            std::string         line;
            std::getline(str, line);

            std::stringstream   lineStream(line);
            std::string         cell;

            m_data.clear();
            while(std::getline(lineStream, cell, ','))
            {
                m_data.push_back(cell);
            }
            // This checks for a trailing comma with no data after it.
            if (!lineStream && cell.empty())
            {
                // If there was a trailing comma then add an empty element.
                m_data.push_back("");
            }
        }
    private:
        std::vector<std::string>    m_data;
};

std::istream& operator>>(std::istream& str, CSVRow& data)
{
    data.readNextRow(str);
    return str;
}   
int main()
{
    std::ifstream       file("plop.csv");

    CSVRow              row;
    while(file >> row)
    {
        std::cout << "4th Element(" << row[3] << ")\n";
    }
}
Run Code Online (Sandbox Code Playgroud)

但是通过一些工作,我们可以在技术上创建一个迭代器:

class CSVIterator
{   
    public:
        typedef std::input_iterator_tag     iterator_category;
        typedef CSVRow                      value_type;
        typedef std::size_t                 difference_type;
        typedef CSVRow*                     pointer;
        typedef CSVRow&                     reference;

        CSVIterator(std::istream& str)  :m_str(str.good()?&str:NULL) { ++(*this); }
        CSVIterator()                   :m_str(NULL) {}

        // Pre Increment
        CSVIterator& operator++()               {if (m_str) { if (!((*m_str) >> m_row)){m_str = NULL;}}return *this;}
        // Post increment
        CSVIterator operator++(int)             {CSVIterator    tmp(*this);++(*this);return tmp;}
        CSVRow const& operator*()   const       {return m_row;}
        CSVRow const* operator->()  const       {return &m_row;}

        bool operator==(CSVIterator const& rhs) {return ((this == &rhs) || ((this->m_str == NULL) && (rhs.m_str == NULL)));}
        bool operator!=(CSVIterator const& rhs) {return !((*this) == rhs);}
    private:
        std::istream*       m_str;
        CSVRow              m_row;
};


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

    for(CSVIterator loop(file); loop != CSVIterator(); ++loop)
    {
        std::cout << "4th Element(" << (*loop)[3] << ")\n";
    }
}
Run Code Online (Sandbox Code Playgroud)

  • first()next().这是什么Java!只是开个玩笑. (18认同)
  • @DarthVader:我认为做出广泛的概括是愚蠢的.上面的代码工作正常,所以我实际上可以看到它有什么问题.但如果您对上述内容有任何具体评论,我肯定会考虑在这方面.但我可以看到你如何通过盲目地遵循一套C#的通用规则并将其应用于另一种语言来得出这个结论. (11认同)
  • 另外,如果你遇到与上面代码奇怪的链接问题,因为另一个库定义了`istream :: operator >>`(就像Eigen),在运算符声明之前添加一个`inline`来修复它. (4认同)
  • @DarthVader:一个覆盖面广泛的声明,其广泛性是愚蠢的.如果你想澄清为什么它是坏的,那么为什么这种不良适用于这种情况. (3认同)
  • 这是如何创建我见过的迭代器类的最简单,最干净的例子. (3认同)
  • @sebastian_k:我认为您对通过添加inline :-(解决的问题感到困惑。应该通过在显式命名空间中放置内容来解决命名空间冲突。inline将帮助链接器解决问题,因为您将定义放在头文件中并多次包含它。 (2认同)
  • 缺少解析部分,仍然以字符串结尾。这只是一个过度设计的线路分离器。 (2认同)
  • @DarVader,当您具有成员别名`typedef std :: input_iterator_tag iterator_category;时,类的* operator!==不正确*不*过载;`参见[InputIterator](http://en.cppreference.com/w / cpp / concept / InputIterator) (2认同)

dtw*_*dtw 46

使用Boost Tokenizer的解决方案:

std::vector<std::string> vec;
using namespace boost;
tokenizer<escaped_list_separator<char> > tk(
   line, escaped_list_separator<char>('\\', ',', '\"'));
for (tokenizer<escaped_list_separator<char> >::iterator i(tk.begin());
   i!=tk.end();++i) 
{
   vec.push_back(*i);
}
Run Code Online (Sandbox Code Playgroud)

  • boost tokenizer并不完全支持完整的CSV标准,但有一些快速的解决方法.见http://stackoverflow.com/questions/1120140/csv-parser-in-c/1595366#1595366 (9认同)
  • @NPike:您可以使用boost附带的[bcp](http://www.boost.org/doc/libs/release/tools/bcp/index.html)实用程序来仅提取您实际需要的标头. (6认同)
  • 您是否必须在您的计算机上安装整个boost库,或者您只是使用其代码的子集来执行此操作?对于CSV解析,256mb似乎很多.. (3认同)

sas*_*nin 42

我的版本除了标准的C++ 11库之外没有使用任何东西.它很好地处理Excel CSV报价:

spam eggs,"foo,bar","""fizz buzz"""
1.23,4.567,-8.00E+09
Run Code Online (Sandbox Code Playgroud)

代码被编写为有限状态机,并且一次消耗一个字符.我认为这更容易推理.

#include <istream>
#include <string>
#include <vector>

enum class CSVState {
    UnquotedField,
    QuotedField,
    QuotedQuote
};

std::vector<std::string> readCSVRow(const std::string &row) {
    CSVState state = CSVState::UnquotedField;
    std::vector<std::string> fields {""};
    size_t i = 0; // index of the current field
    for (char c : row) {
        switch (state) {
            case CSVState::UnquotedField:
                switch (c) {
                    case ',': // end of field
                              fields.push_back(""); i++;
                              break;
                    case '"': state = CSVState::QuotedField;
                              break;
                    default:  fields[i].push_back(c);
                              break; }
                break;
            case CSVState::QuotedField:
                switch (c) {
                    case '"': state = CSVState::QuotedQuote;
                              break;
                    default:  fields[i].push_back(c);
                              break; }
                break;
            case CSVState::QuotedQuote:
                switch (c) {
                    case ',': // , after closing quote
                              fields.push_back(""); i++;
                              state = CSVState::UnquotedField;
                              break;
                    case '"': // "" -> "
                              fields[i].push_back('"');
                              state = CSVState::QuotedField;
                              break;
                    default:  // end of quote
                              state = CSVState::UnquotedField;
                              break; }
                break;
        }
    }
    return fields;
}

/// Read CSV file, Excel dialect. Accept "quoted fields ""with quotes"""
std::vector<std::vector<std::string>> readCSV(std::istream &in) {
    std::vector<std::vector<std::string>> table;
    std::string row;
    while (!in.eof()) {
        std::getline(in, row);
        if (in.bad() || in.fail()) {
            break;
        }
        auto fields = readCSVRow(row);
        table.push_back(fields);
    }
    return table;
}
Run Code Online (Sandbox Code Playgroud)

  • 谢谢,我认为这是最完整的答案,太糟糕了,它埋在这里. (5认同)

小智 31

C++字符串工具箱库(StrTk)有一个令牌网类,允许你加载无论是从数据的文本文件,字符串或字符缓冲区,并在行列时尚解析/处理它们.

您可以指定行分隔符和列分隔符,也可以仅使用默认值.

void foo()
{
   std::string data = "1,2,3,4,5\n"
                      "0,2,4,6,8\n"
                      "1,3,5,7,9\n";

   strtk::token_grid grid(data,data.size(),",");

   for(std::size_t i = 0; i < grid.row_count(); ++i)
   {
      strtk::token_grid::row_type r = grid.row(i);
      for(std::size_t j = 0; j < r.size(); ++j)
      {
         std::cout << r.get<int>(j) << "\t";
      }
      std::cout << std::endl;
   }
   std::cout << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

更多例子可以在这里找到


Joe*_*man 29

使用Spirit解析CSV并不过分.Spirit非常适合微解析任务.例如,使用Spirit 2.1,它就像:

bool r = phrase_parse(first, last,

    //  Begin grammar
    (
        double_ % ','
    )
    ,
    //  End grammar

    space, v);
Run Code Online (Sandbox Code Playgroud)

矢量v充满了这些值. Boost 1.41刚刚发布的新Spirit 2.1文档中有一系列的教程涉及到这一点.

教程从简单到复杂.CSV解析器位于中间的某个位置,并涉及使用Spirit的各种技术.生成的代码与手写代码一样紧密.看看生成的汇编程序!

  • 实际上它是矫枉过正,编译时间很长,并且使用Spirit进行简单的"微解析任务"是不合理的. (17认同)
  • 另外我想指出上面的代码不解析CSV,它只解析由逗号分隔的向量类型的范围.它不处理引号,不同类型的列等.总之,对于完全回答这个问题的东西的19票似乎对我有点怀疑. (12认同)
  • @Gerdiner我必须同意你的使用精神的开销,就像cvs处理那么简单. (11认同)
  • @konrad:简单地在一个只有一个main的空文件中包含"#include <boost/spirit/include/qi.hpp>",在运行于2.ghz的corei7上,MSVC 2012只需要9.7秒.这是不必要的臃肿.接受的答案在同一台机器上的2秒内编译,我不想想"正确的"Boost.Spirit示例需要多长时间才能编译. (10认同)
  • @Gerdiner废话.小解析器的编译时间不是那么大,但它也无关紧要,因为你将代码填充到它自己的编译单元中并将其编译*一次*.然后你只需要链接它,它就像它获得的那样高效.至于你的其他评论,有多少种方言的CSV和它的处理器.这个当然不是一个非常有用的方言,但它可以简单地扩展到处理引用的值. (9认同)
  • @Gerdiner它对我来说要少得多(约4秒),但就像我说的那样,这是无关紧要的,因为你只需要编译一次TU.保存实现解析器的时间很容易抵消编译成本.至于"正确的"Boost.Sprit语法:一个大的语法可能需要几分钟才能编译.但是再次说明:编写解析器很容易抵消成本,这不是一个持续的成本,因为每次重新编译客户端代码时都不需要重新编译解析器. (4认同)
  • @ArthurChamz实际上,精神是非常有效的.是的,编译需要很长时间,但执行速度通常超过手写解析器,甚至是非天真的实现.为了说明,`boost :: qi :: int_`解析器是[迄今为止所有现有库中最有效的方法,包括手写代码](http://www.kumobius.com/2013/08/c-字符串到INT /). (2认同)
  • @KonradRudolph哇,是这样的吗?我印象深刻!我最终在测试中使用它进行csv解析,效果很好! (2认同)
  • 该示例显示了逗号分隔的双打列表,而不是 CSV。 (2认同)

ste*_*anB 29

您可以使用带有escaped_list_separator的Boost Tokenizer.

escaped_list_separator解析csv的超集.升压::标记生成器

这只使用Boost tokenizer头文件,没有链接到所需的boost库.

下面是一个示例(有关详细信息,请参阅使用C++中的Boost Tokenizer解析CSV文件Boost::tokenizer):

#include <iostream>     // cout, endl
#include <fstream>      // fstream
#include <vector>
#include <string>
#include <algorithm>    // copy
#include <iterator>     // ostream_operator
#include <boost/tokenizer.hpp>

int main()
{
    using namespace std;
    using namespace boost;
    string data("data.csv");

    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());

        // vector now contains strings from one row, output to cout here
        copy(vec.begin(), vec.end(), ostream_iterator<string>(cout, "|"));

        cout << "\n----------------------" << endl;
    }
}
Run Code Online (Sandbox Code Playgroud)


小智 17

如果你不要在意正确解析CSV,因为它的工作原理一个字符在同一时间,这将做...相对缓慢.

 void ParseCSV(const string& csvSource, vector<vector<string> >& lines)
    {
       bool inQuote(false);
       bool newLine(false);
       string field;
       lines.clear();
       vector<string> line;

       string::const_iterator aChar = csvSource.begin();
       while (aChar != csvSource.end())
       {
          switch (*aChar)
          {
          case '"':
             newLine = false;
             inQuote = !inQuote;
             break;

          case ',':
             newLine = false;
             if (inQuote == true)
             {
                field += *aChar;
             }
             else
             {
                line.push_back(field);
                field.clear();
             }
             break;

          case '\n':
          case '\r':
             if (inQuote == true)
             {
                field += *aChar;
             }
             else
             {
                if (newLine == false)
                {
                   line.push_back(field);
                   lines.push_back(line);
                   field.clear();
                   line.clear();
                   newLine = true;
                }
             }
             break;

          default:
             newLine = false;
             field.push_back(*aChar);
             break;
          }

          aChar++;
       }

       if (field.size())
          line.push_back(field);

       if (line.size())
          lines.push_back(line);
    }
Run Code Online (Sandbox Code Playgroud)


Rol*_*sen 14

使用Boost Tokenizer escaped_list_separator获取CSV文件时,应该注意以下事项:

  1. 它需要一个转义字符(默认反斜杠 - \)
  2. 它需要一个分隔符/分隔符 - 字符(默认逗号 - ,)
  3. 它需要一个引号字符(默认引号 - ")

wiki指定的CSV格式声明数据字段可以包含引号中的分隔符(支持):

1997年,福特,E350,"超级豪华卡车"

wiki指定的CSV格式声明单引号应使用双引号处理(escaped_list_separator将删除所有引号字符):

1997年,福特,E350,"超级""豪华""卡车"

CSV格式未指定应删除任何反斜杠字符(escaped_list_separator将删除所有转义字符).

可能的解决方法是修复boost escaped_list_separator的默认行为:

  1. 首先用两个反斜杠字符(\\)替换所有反斜杠字符(\),这样它们就不会被剥离.
  2. 其次用一个反斜杠字符和一个引号(\")替换所有双引号("")

这种解决方法具有副作用,即由双引号表示的空数据字段将转换为单引号令牌.迭代令牌时,必须检查令牌是否是单引号,并将其视为空字符串.

虽然引号内没有换行符,但不是很漂亮但它有效.


小智 7

您可能希望查看我的FOSS项目CSVfix(更新链接),它是一个用C++编写的CSV流编辑器.CSV解析器不是奖品,但是在没有编写任何代码的情况下,工作和整个软件包可以满足您的需求.

alib/SRC/a_csv.cpp用于CSV解析器和csvlib/SRC/csved_ioman.cpp(IOManager::ReadCSV用于使用示例).


jxh*_*jxh 7

由于所有CSV问题似乎都被重定向到这里,我想我会在这里发布我的答案.这个答案没有直接解决提问者的问题.我希望能够读取已知为CSV格式的流,并且每个字段的类型也已知.当然,下面的方法可以用于将每个字段视为字符串类型.

作为我希望如何使用CSV输入流的示例,请考虑以下输入(取自维基百科的CSV页面):

const char input[] =
"Year,Make,Model,Description,Price\n"
"1997,Ford,E350,\"ac, abs, moon\",3000.00\n"
"1999,Chevy,\"Venture \"\"Extended Edition\"\"\",\"\",4900.00\n"
"1999,Chevy,\"Venture \"\"Extended Edition, Very Large\"\"\",\"\",5000.00\n"
"1996,Jeep,Grand Cherokee,\"MUST SELL!\n\
air, moon roof, loaded\",4799.00\n"
;
Run Code Online (Sandbox Code Playgroud)

然后,我希望能够读取这样的数据:

std::istringstream ss(input);
std::string title[5];
int year;
std::string make, model, desc;
float price;
csv_istream(ss)
    >> title[0] >> title[1] >> title[2] >> title[3] >> title[4];
while (csv_istream(ss)
       >> year >> make >> model >> desc >> price) {
    //...do something with the record...
}
Run Code Online (Sandbox Code Playgroud)

这是我最终解决的问题.

struct csv_istream {
    std::istream &is_;
    csv_istream (std::istream &is) : is_(is) {}
    void scan_ws () const {
        while (is_.good()) {
            int c = is_.peek();
            if (c != ' ' && c != '\t') break;
            is_.get();
        }
    }
    void scan (std::string *s = 0) const {
        std::string ws;
        int c = is_.get();
        if (is_.good()) {
            do {
                if (c == ',' || c == '\n') break;
                if (s) {
                    ws += c;
                    if (c != ' ' && c != '\t') {
                        *s += ws;
                        ws.clear();
                    }
                }
                c = is_.get();
            } while (is_.good());
            if (is_.eof()) is_.clear();
        }
    }
    template <typename T, bool> struct set_value {
        void operator () (std::string in, T &v) const {
            std::istringstream(in) >> v;
        }
    };
    template <typename T> struct set_value<T, true> {
        template <bool SIGNED> void convert (std::string in, T &v) const {
            if (SIGNED) v = ::strtoll(in.c_str(), 0, 0);
            else v = ::strtoull(in.c_str(), 0, 0);
        }
        void operator () (std::string in, T &v) const {
            convert<is_signed_int<T>::val>(in, v);
        }
    };
    template <typename T> const csv_istream & operator >> (T &v) const {
        std::string tmp;
        scan(&tmp);
        set_value<T, is_int<T>::val>()(tmp, v);
        return *this;
    }
    const csv_istream & operator >> (std::string &v) const {
        v.clear();
        scan_ws();
        if (is_.peek() != '"') scan(&v);
        else {
            std::string tmp;
            is_.get();
            std::getline(is_, tmp, '"');
            while (is_.peek() == '"') {
                v += tmp;
                v += is_.get();
                std::getline(is_, tmp, '"');
            }
            v += tmp;
            scan();
        }
        return *this;
    }
    template <typename T>
    const csv_istream & operator >> (T &(*manip)(T &)) const {
        is_ >> manip;
        return *this;
    }
    operator bool () const { return !is_.fail(); }
};
Run Code Online (Sandbox Code Playgroud)

使用C++ 11中新的整数特征模板可以简化的以下助手:

template <typename T> struct is_signed_int { enum { val = false }; };
template <> struct is_signed_int<short> { enum { val = true}; };
template <> struct is_signed_int<int> { enum { val = true}; };
template <> struct is_signed_int<long> { enum { val = true}; };
template <> struct is_signed_int<long long> { enum { val = true}; };

template <typename T> struct is_unsigned_int { enum { val = false }; };
template <> struct is_unsigned_int<unsigned short> { enum { val = true}; };
template <> struct is_unsigned_int<unsigned int> { enum { val = true}; };
template <> struct is_unsigned_int<unsigned long> { enum { val = true}; };
template <> struct is_unsigned_int<unsigned long long> { enum { val = true}; };

template <typename T> struct is_int {
    enum { val = (is_signed_int<T>::val || is_unsigned_int<T>::val) };
};
Run Code Online (Sandbox Code Playgroud)


Ale*_*der 7

您可以使用仅标头的Csv::Parser库。

  • 它完全支持 RFC 4180,包括引用值、转义引号和字段值中的换行符。
  • 它仅需要标准 C++ (C++17)。
  • std::string_view它支持在编译时读取 CSV 数据。
  • 它使用Catch2进行了广泛的测试。


sas*_*alm 6

这是 Unicode CSV 解析器的另一个实现(与 wchar_t 一起使用)。我写了其中的一部分,乔纳森·莱夫勒写了其余的部分。

注意:此解析器旨在尽可能地复制 Excel 的行为,特别是在导入损坏或格式错误的CSV 文件时。

这是最初的问题 - Parsing CSV file with multiline fields and escaped doublequotes

这是 SSCCE(简短、自包含、正确示例)的代码。

#include <stdbool.h>
#include <wchar.h>
#include <wctype.h>

extern const wchar_t *nextCsvField(const wchar_t *p, wchar_t sep, bool *newline);

// Returns a pointer to the start of the next field,
// or zero if this is the last field in the CSV
// p is the start position of the field
// sep is the separator used, i.e. comma or semicolon
// newline says whether the field ends with a newline or with a comma
const wchar_t *nextCsvField(const wchar_t *p, wchar_t sep, bool *newline)
{
    // Parse quoted sequences
    if ('"' == p[0]) {
        p++;
        while (1) {
            // Find next double-quote
            p = wcschr(p, L'"');
            // If we don't find it or it's the last symbol
            // then this is the last field
            if (!p || !p[1])
                return 0;
            // Check for "", it is an escaped double-quote
            if (p[1] != '"')
                break;
            // Skip the escaped double-quote
            p += 2;
        }
    }

    // Find next newline or comma.
    wchar_t newline_or_sep[4] = L"\n\r ";
    newline_or_sep[2] = sep;
    p = wcspbrk(p, newline_or_sep);

    // If no newline or separator, this is the last field.
    if (!p)
        return 0;

    // Check if we had newline.
    *newline = (p[0] == '\r' || p[0] == '\n');

    // Handle "\r\n", otherwise just increment
    if (p[0] == '\r' && p[1] == '\n')
        p += 2;
    else
        p++;

    return p;
}

static wchar_t *csvFieldData(const wchar_t *fld_s, const wchar_t *fld_e, wchar_t *buffer, size_t buflen)
{
    wchar_t *dst = buffer;
    wchar_t *end = buffer + buflen - 1;
    const wchar_t *src = fld_s;

    if (*src == L'"')
    {
        const wchar_t *p = src + 1;
        while (p < fld_e && dst < end)
        {
            if (p[0] == L'"' && p+1 < fld_s && p[1] == L'"')
            {
                *dst++ = p[0];
                p += 2;
            }
            else if (p[0] == L'"')
            {
                p++;
                break;
            }
            else
                *dst++ = *p++;
        }
        src = p;
    }
    while (src < fld_e && dst < end)
        *dst++ = *src++;
    if (dst >= end)
        return 0;
    *dst = L'\0';
    return(buffer);
}

static void dissect(const wchar_t *line)
{
    const wchar_t *start = line;
    const wchar_t *next;
    bool     eol;
    wprintf(L"Input %3zd: [%.*ls]\n", wcslen(line), wcslen(line)-1, line);
    while ((next = nextCsvField(start, L',', &eol)) != 0)
    {
        wchar_t buffer[1024];
        wprintf(L"Raw Field: [%.*ls] (eol = %d)\n", (next - start - eol), start, eol);
        if (csvFieldData(start, next-1, buffer, sizeof(buffer)/sizeof(buffer[0])) != 0)
            wprintf(L"Field %3zd: [%ls]\n", wcslen(buffer), buffer);
        start = next;
    }
}

static const wchar_t multiline[] =
   L"First field of first row,\"This field is multiline\n"
    "\n"
    "but that's OK because it's enclosed in double quotes, and this\n"
    "is an escaped \"\" double quote\" but this one \"\" is not\n"
    "   \"This is second field of second row, but it is not multiline\n"
    "   because it doesn't start \n"
    "   with an immediate double quote\"\n"
    ;

int main(void)
{
    wchar_t line[1024];

    while (fgetws(line, sizeof(line)/sizeof(line[0]), stdin))
        dissect(line);
    dissect(multiline);

    return 0;
}
Run Code Online (Sandbox Code Playgroud)


m0m*_*eni 6

我编写了仅标头的C ++ 11 CSV解析器。它经过了良好的测试,速度很快,支持整个CSV规范(带引号的字段,引号中的定界符/终止符,引号转义等),并且可以配置为解决不符合该规范的CSV。

通过流畅的界面进行配置:

// constructor accepts any input stream
CsvParser parser = CsvParser(std::cin)
  .delimiter(';')    // delimited by ; instead of ,
  .quote('\'')       // quoted fields use ' instead of "
  .terminator('\0'); // terminated by \0 instead of by \r\n, \n, or \r
Run Code Online (Sandbox Code Playgroud)

解析只是基于for循环的范围:

#include <iostream>
#include "../parser.hpp"

using namespace aria::csv;

int main() {
  std::ifstream f("some_file.csv");
  CsvParser parser(f);

  for (auto& row : parser) {
    for (auto& field : row) {
      std::cout << field << " | ";
    }
    std::cout << std::endl;
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 干得好,但您还需要添加三件事:(1)读取标头(2)提供按名称索引的字段(3)不要通过重用相同的字符串向量来重新分配循环中的内存 (2认同)
  • @MaksymGanenko 啊我明白你的意思了。当您在编译时知道 CSV 的列时,可以使用 https://github.com/ben-strasser/fast-cpp-csv-parser,它可能比我的更好。我想要的是一个 CSV 解析器,适用于您想要对许多不同的 CSV 使用相同的代码并且提前不知道它们是什么样子的情况。所以我可能不会添加#2,但我会在将来的某个时候添加#1。 (2认同)

Hey*_*sch 5

可在此处找到另一个CSV I/O库:

http://code.google.com/p/fast-cpp-csv-parser/

#include "csv.h"

int main(){
  io::CSVReader<3> in("ram.csv");
  in.read_header(io::ignore_extra_column, "vendor", "size", "speed");
  std::string vendor; int size; double speed;
  while(in.read_row(vendor, size, speed)){
    // do stuff with the data
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 很好,但它会强制您在编译时选择列数.对许多应用程序来说不太有用. (2认同)

Pie*_*ica 5

另一种解决方案类似于Loki Astari的答案,在C++ 11中.这里std::tuple的行是给定类型的.代码扫描一行,然后扫描直到每个分隔符,然后将值直接转换并转储到元组中(带有一些模板代码).

for (auto row : csv<std::string, int, float>(file, ',')) {
    std::cout << "first col: " << std::get<0>(row) << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

Advanges:

  • 非常简洁易用,只有C++ 11.
  • 自动类型转换为std::tuple<t1, ...>via operator>>.

少了什么东西:

  • 逃避和引用
  • 格式错误的CSV时无法处理错误.

主要代码:

#include <iterator>
#include <sstream>
#include <string>

namespace csvtools {
    /// Read the last element of the tuple without calling recursively
    template <std::size_t idx, class... fields>
    typename std::enable_if<idx >= std::tuple_size<std::tuple<fields...>>::value - 1>::type
    read_tuple(std::istream &in, std::tuple<fields...> &out, const char delimiter) {
        std::string cell;
        std::getline(in, cell, delimiter);
        std::stringstream cell_stream(cell);
        cell_stream >> std::get<idx>(out);
    }

    /// Read the @p idx-th element of the tuple and then calls itself with @p idx + 1 to
    /// read the next element of the tuple. Automatically falls in the previous case when
    /// reaches the last element of the tuple thanks to enable_if
    template <std::size_t idx, class... fields>
    typename std::enable_if<idx < std::tuple_size<std::tuple<fields...>>::value - 1>::type
    read_tuple(std::istream &in, std::tuple<fields...> &out, const char delimiter) {
        std::string cell;
        std::getline(in, cell, delimiter);
        std::stringstream cell_stream(cell);
        cell_stream >> std::get<idx>(out);
        read_tuple<idx + 1, fields...>(in, out, delimiter);
    }
}

/// Iterable csv wrapper around a stream. @p fields the list of types that form up a row.
template <class... fields>
class csv {
    std::istream &_in;
    const char _delim;
public:
    typedef std::tuple<fields...> value_type;
    class iterator;

    /// Construct from a stream.
    inline csv(std::istream &in, const char delim) : _in(in), _delim(delim) {}

    /// Status of the underlying stream
    /// @{
    inline bool good() const {
        return _in.good();
    }
    inline const std::istream &underlying_stream() const {
        return _in;
    }
    /// @}

    inline iterator begin();
    inline iterator end();
private:

    /// Reads a line into a stringstream, and then reads the line into a tuple, that is returned
    inline value_type read_row() {
        std::string line;
        std::getline(_in, line);
        std::stringstream line_stream(line);
        std::tuple<fields...> retval;
        csvtools::read_tuple<0, fields...>(line_stream, retval, _delim);
        return retval;
    }
};

/// Iterator; just calls recursively @ref csv::read_row and stores the result.
template <class... fields>
class csv<fields...>::iterator {
    csv::value_type _row;
    csv *_parent;
public:
    typedef std::input_iterator_tag iterator_category;
    typedef csv::value_type         value_type;
    typedef std::size_t             difference_type;
    typedef csv::value_type *       pointer;
    typedef csv::value_type &       reference;

    /// Construct an empty/end iterator
    inline iterator() : _parent(nullptr) {}
    /// Construct an iterator at the beginning of the @p parent csv object.
    inline iterator(csv &parent) : _parent(parent.good() ? &parent : nullptr) {
        ++(*this);
    }

    /// Read one row, if possible. Set to end if parent is not good anymore.
    inline iterator &operator++() {
        if (_parent != nullptr) {
            _row = _parent->read_row();
            if (!_parent->good()) {
                _parent = nullptr;
            }
        }
        return *this;
    }

    inline iterator operator++(int) {
        iterator copy = *this;
        ++(*this);
        return copy;
    }

    inline csv::value_type const &operator*() const {
        return _row;
    }

    inline csv::value_type const *operator->() const {
        return &_row;
    }

    bool operator==(iterator const &other) {
        return (this == &other) or (_parent == nullptr and other._parent == nullptr);
    }
    bool operator!=(iterator const &other) {
        return not (*this == other);
    }
};

template <class... fields>
typename csv<fields...>::iterator csv<fields...>::begin() {
    return iterator(*this);
}

template <class... fields>
typename csv<fields...>::iterator csv<fields...>::end() {
    return iterator();
}
Run Code Online (Sandbox Code Playgroud)

我在GitHub上放了一个小小的工作示例; 我一直用它来解析一些数值数据,并且它有用.