C++:简约CSV流

开课吧小一2021-05-07 09:56

点赞
有用
分享分享

MiniCSV是一个小的单头文件库,它基于C ++文件流,并且相对易于使用。事不宜迟,让我们看看实际的代码。

写作

我们看到一个使用csv::ofstreamclass将制表符分隔的值写入文件的示例。现在,您可以set_delimiter在1.7版中进行调用时指定转义字符串。

#include "minicsv.h"struct Product
{
    Product() : name(""), qty(0), price(0.0f) {}
    Product(std::string name_, int qty_, float price_) 
        : name(name_), qty(qty_), price(price_) {}
    std::string name;
    int qty;
    float price;
};
int main()
{
    csv::ofstream os("products.txt");
    os.set_delimiter('\t', "##");
    if(os.is_open())
    {
        Product product("Shampoo", 200, 15.0f);
        os << product.name << product.qty << product.price << NEWLINE;
        Product product2("Soap", 300, 6.0f);
        os << product2.name << product2.qty << product2.price << NEWLINE;
    }
    os.flush();
    return 0;
}

NEWLINE定义为'\n'。我们不能std::endl在这里使用,因为csv::ofstream它不是从派生的std::ofstream。

要回读相同的文件,该文件csv::ifstream用于std::cout在控制台上显示读取的项目。

#include "minicsv.h"#include <iostream>int main()
{
    csv::ifstream is("products.txt");
    is.set_delimiter('\t', "##");
    if(is.is_open())
    {
        Product temp;
        while(is.read_line())
        {
            is >> temp.name >> temp.qty >> temp.price;
            // display the read items            std::cout << temp.name << "," << temp.qty << "," << temp.price << std::endl;
        }
    }
    return 0;
}

控制台中的输出如下:

Shampoo,200,15
Soap,300,6

重载的流运算符

字符串流已在v1.6中引入。让我向您展示一个有关如何重载Product该类的字符串流运算符的示例。文件流的概念相同。

#include "minicsv.h"#include <iostream>struct Product
{
    Product() : name(""), qty(0), price(0.0f) {}
    Product(std::string name_, int qty_, float price_) : name(name_), 
                               qty(qty_), price(price_) {}
    std::string name;
    int qty;
    float price;
};

template<>inline csv::istringstream& operator >> (csv::istringstream& istm, Product& val)
{
    return istm >> val.name >> val.qty >> val.price;
}

template<>inline csv::ostringstream& operator << (csv::ostringstream& ostm, const Product& val)
{
    return ostm << val.name << val.qty << val.price;
}
int main()
{
    // test string streams using overloaded stream operators for Product    {
        csv::ostringstream os;
        os.set_delimiter(',', "$$");
        Product product("Shampoo", 200, 15.0f);
        os << product << NEWLINE;
        Product product2("Towel, Soap, Shower Foam", 300, 6.0f);
        os << product2 << NEWLINE;

        csv::istringstream is(os.get_text().c_str());
        is.set_delimiter(',', "$$");
        Product prod;
        while (is.read_line())
        {
            is >> prod;
            // display the read items            std::cout << prod.name << "|" << prod.qty << "|" << prod.price << std::endl;
        }
    }
    return 0;
}

这就是控制台上显示的内容。

Shampoo|200|15
Towel, Soap, Shower Foam|300|6

如果该类型有private成员该怎么办?创建一个接受stream对象的成员函数。

class Product
{public:
    void read(csv::istringstream& istm)
    {
        istm >> this->name >> this->qty >> this->price;
    }
};

template<>inline csv::istringstream& operator >> (csv::istringstream& istm, Product& prod)
{
    prod.read(istm);
    return istm;
}

结论

MiniCSV是基于C ++文件流的小型CSV库。因为分隔符可以随时更改,所以与没有库帮助的手写方式相比,我使用此库在相对较短的时间内为MTL和Wavefront OBJ格式编写了文件解析器。

历史

2014-03-09:初始版本

2014-08-20:删除使用智能ptr

2015-03-23:通过删除每一行上的刷新来提高写性能75%,修复了多次重新定义的lnk2005错误。read_line替换eof为ifstream。

2015-09-22:v1.7:文本上的转义/不转义和环绕/修剪引号

2015-09-24:添加了重载stringstream运算符示例。

2015-09-27:const char*v1.7.2中的Stream运算符重载

2015-10-04:修复了v1.7.3中的G ++和Clang ++编译错误。

2015-10-20:enable_trim_quote_on_str在v1.7.6中启用时,在阅读过程中忽略引号内的定界符。例如:10.0,“ Bottle,Cup,Teaspoon”,123.0将被读取为3个标记:<10.0> <Bottle,Cup,Teaspoon> <123.0>

2016-05-05:现在,已将引号字符串中的引号转义。默认转义字符串是"&quot;"可以通过os.enable_surround_quote_on_str()和更改的字符串is.enable_trim_quote_on_str()

2016-07-10:版本1.7.9:读取UTF-8 BOM

2016-08-02:版本1.7.10:流的分隔符类,因此,set_delimiter如果分隔符不断变化,则无需重复调用。请参见下面的代码示例:

// demo sep class usagecsv::istringstream is("vt:33,44,66");
is.set_delimiter(',', "$$");
csv::sep colon(':', "<colon>");
csv::sep comma(',', "<comma>");while (is.read_line())
{
    std::string type;
    int r = 0, b = 0, g = 0;
    is >> colon >> type >> comma >> r >> b >> g;
    // display the read items    std::cout << type << "|" << r << "|" << b << "|" << g << std::endl;
}

2016-08-23:版本1.7.11:已修复num_of_delimiter功能:不将引号中的定界符计算在内

2016-08-26:版本1.8.0:为读取期间的数据转换添加了更好的错误消息。在此之前,std::istringstream未检测到数据转换错误。

更改之前:

template<typename T>
csv::ifstream& operator >> (csv::ifstream& istm, T& val)
{
    std::string str = istm.get_delimited_str();
    #ifdef USE_BOOST_LEXICAL_CAST    val = boost::lexical_cast<T>(str);#else    std::istringstream is(str);
    is >> val;#endif
    return istm;
}

更改后:

template<typename T>
csv::ifstream& operator >> (csv::ifstream& istm, T& val)
{
    std::string str = istm.get_delimited_str();
#ifdef USE_BOOST_LEXICAL_CAST    try 
    {
        val = boost::lexical_cast<T>(str);
    }
    catch (boost::bad_lexical_cast& e)
    {
        throw std::runtime_error(istm.error_line(str).c_str());
    }#else    std::istringstream is(str);
    is >> val;
    if (!(bool)is)
    {
        throw std::runtime_error(istm.error_line(str).c_str());
    }#endif
    return istm;
}

重大更改:这意味着boost::bad_lexical_cast必须更改要捕获的旧用户代码std::runtime_error。相同csv::istringstream。当心std::istringstream不如boost::lexical_cast捕捉错误。示例,"4a"将其转换为整数4而没有错误。错误日志

示例csv::ifstream如下:

csv::ifstream conversion error at line no.:2, 
filename:products.txt, token position:3, token:aa

相似之处csv::istringstream在于没有文件名。

csv::istringstream conversion error at line no.:2, token position:3, token:aa

2017-01-08:1.8.2版本具有更好的输入流性能 运行基准测试以查看(注:需要首先更新驱动器/文件夹的位置)。

针对1.8.0版的基准测试结果:

 mini_180::csv::ofstream:  348ms
     mini_180::csv::ifstream:  339ms <<< v1.8.0
         mini::csv::ofstream:  347ms
         mini::csv::ifstream:  308ms <<< v1.8.2
mini_180::csv::ostringstream:  324ms
mini_180::csv::istringstream:  332ms <<< v1.8.0
    mini::csv::ostringstream:  325ms
    mini::csv::istringstream:  301ms <<< v1.8.2

2017-01-23:版本1.8.3添加了单元测试,并允许2个引号转义1个引号符合CSV规范。

2017-02-07:版本1.8.3b添加更多的单元测试并删除CPOL许可证文件。

2017年3月12日:版本1.8.4固定一些char输出问题,并加入NChar(char包装)类,以写入到数字值[-127..128]到char变量。

bool test_nchar(bool enable_quote)
{
    csv::ostringstream os;
    os.set_delimiter(',', "$$");
    os.enable_surround_quote_on_str(enable_quote, '\"');

    os << "Wallet" << 56 << NEWLINE;

    csv::istringstream is(os.get_text().c_str());
    is.set_delimiter(',', "$$");
    is.enable_trim_quote_on_str(enable_quote, '\"');

    while (is.read_line())
    {
        try
        {
            std::string dest_name = "";
            char dest_char = 0;

            is >> dest_name >> csv::NChar(dest_char);

            std::cout << dest_name << ", " 
                << (int)dest_char << std::endl;
        }
        catch (std::runtime_error& e)
        {
            std::cerr << __FUNCTION__ << e.what() << std::endl;
        }
    }
    return true;
}

显示输出:

Wallet, 56

2017-09-18:版本1.8.5:

如果您的转义参数set_delimiter()为空,带分隔符的文本将自动用引号引起来(以符合Microsoft Excel和常规CSV惯例)。

"Hello,World",600

Microsoft Excel和MiniCSV将此读为Hello,World“和” 600。

2021年2月21日:版本1.8.5d:在固定的无限循环quote_unescape。

2021-05-06:MiniCSV在存在换行符的情况下检测到行尾。字符串输入中的换行符不可避免地破坏了解析。新版本1.8.6通过转义符来保护换行符。

兴趣点

最近,我遇到了一个有趣的基准测试结果,即读取5MB文件,与string_viewVincent La的CSV解析器进行对比。您可以看到短字符串缓冲区(SSO)的效果。

每列的基准长度为12个字符

该长度在SSO限制之内(24个字节),以避免堆分配。

csv_parser timing:113ms
MiniCSV timing:71ms
CSV Stream timing:187ms

每列的基准长度为30个字符

长度超出SSO限制,必须在堆上分配内存!现在string_view csv_parser赢了。

csv_parser timing:147ms
MiniCSV timing:175ms
CSV Stream timing:434ms

注意:通过这种方式我不确定为什么VC ++ 15.9更新中的CSV Stream这么慢。

注意:基准测试可能与其他C ++编译器(例如G ++和Clang ++)有所不同,我现在无法访问。

以上就是小编为大家整理的“C++:简约CSV流”一文,更多相关信息尽在开课吧C++教程频道。

相关推荐:

免费领完整的AI学习路径资料,带你轻松入门!

AI资料难找吗?AI免费论文资料,等你领取!

福利来袭!人工智能核心课程优惠名额等你来领

有用
分享