65.9K
CodeProject 正在变化。 阅读更多。
Home

C++14: 基于 C 文件 API 的 CSV 流

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.69/5 (12投票s)

2017年1月28日

CPOL

3分钟阅读

viewsIcon

23917

downloadIcon

384

C++14: 基于 C 文件 API 的 CSV 流,用于移除 STL 文件流的代码膨胀

目录

主要动机

该库基于 C 文件 API。 其目的是减少 C++ STL 流带来的代码膨胀。 它的用法类似于 Minimalistic CSV Stream,该流基于 C++ 文件流,同样是一个仅头文件的库。 只需要将命名空间从 mini 更改为 capi。 其部分优化已反向移植到 Minimalistic CSV Stream 版本 1.8.3,包括尽可能通过引用传递、使用数据成员缓存结果,并避免返回新的 string 对象的运算。 读者可以比较 v1.8.2 和 v1.8.3 之间的差异。

澄清误解

仅使用此类并不能减少应用程序中的代码膨胀。 只有当所有其他 fstreamstringstreamcout/cin 调用被删除或替换为非 STL 流等效项时,才能实现这一点。

重大更改

如果您为自定义数据类型重载 STL 流运算符,而不是 CSV 流运算符,则该类不能直接替换 MiniCSV。 您必须重载 CSV 流运算符。

可选依赖项

Boost Spirit Qi v2

要使用 Boost Spirit Qi 进行字符串到数据的转换,请在包含头文件之前定义 USE_BOOST_SPIRIT_QI

#define USE_BOOST_SPIRIT_QI
#include "csv_stream.h"

要将 char 读取为 ASCII 而不是整数,请在包含头文件之前定义 CHAR_AS_ASCII

#define CHAR_AS_ASCII
#include "csv_stream.h"

警告: 此宏检测在 v0.5.2 中已被删除,因为它是一个全局范围的设置。 对于希望将 char 读/写为数字 8 位整数的用户,请使用 NChar 类。 使用 os << csv::NChar(ch) 进行写入,但用户可以将其转换为 int 而无需使用 NChar。 以及 is >> csv::NChar(ch) 用于将范围从 -127 到 128 的整数读入 char 变量。

基准测试

注意: 基准测试结果基于最新的 minicsv v1.8.2。
注意: 各种方法仅影响输入流基准测试结果。

文件流基准测试

           // minicsv using std::stringstream
           mini::csv::ofstream:  387ms
           mini::csv::ifstream:  386ms
           // minicsv using Boost lexical_cast
           mini::csv::ofstream:  405ms
           mini::csv::ifstream:  283ms
           // capi csv using to_string
           capi::csv::ofstream:  152ms
           capi::csv::ifstream:  279ms
           // capi csv using Boost Spirit Qi
           capi::csv::ofstream:  163ms
           capi::csv::ifstream:  266ms
           // capi in-memory cached file csv
     capi::csv::ocachedfstream:  124ms
     capi::csv::icachedfstream:  127ms
           // capi in-memory cached file csv using Boost Spirit Qi
     capi::csv::ocachedfstream:  122ms
     capi::csv::icachedfstream:  100ms

注意: 内存输入流意味着在处理之前将整个文件加载到内存中。
注意: 内存输出流意味着在保存之前将内容保存在内存中。
注意: 内存流需要足够的内存来将文件内容保存在内存中。

字符串流基准测试

      // minicsv using std::stringstream
      mini::csv::ostringstream:  362ms
      mini::csv::istringstream:  377ms
      // minicsv using Boost lexical_cast
      mini::csv::ostringstream:  383ms
      mini::csv::istringstream:  283ms
      // capi csv
      capi::csv::ostringstream:  113ms
      capi::csv::istringstream:  127ms
      // capi csv using Boost Spirit Qi
      capi::csv::ostringstream:  116ms
      capi::csv::istringstream:  106ms

注意事项

实例化可能很慢,因为要初始化的数据成员很多。

文件流示例代码

#include "csv_stream.h"

using namespace capi;

csv::ofstream os("products.txt");
os.set_delimiter(',', "$$");
os.enable_surround_quote_on_str(true, '\"');
if (os.is_open())
{
    os << "Shampoo" << 200 << 15.0f << NEWLINE;
    os << "Towel" << 300 << 6.0f << NEWLINE;
}
os.flush();
os.close();

csv::ifstream is("products.txt");
is.set_delimiter(',', "$$");
is.enable_trim_quote_on_str(true, '\"');

if (is.is_open())
{
    std::string name = "";
    int qty = 0;
    float price = 0.0f;
    while (is.read_line())
    {
        try
        {
            is >> name >> qty >> price;
            // display the read items
            std::cout << name << "," << qty 
                      << "," << price << std::endl;
        }
        catch (std::runtime_error& e)
        {
            std::cerr << e.what() << std::endl;
        }
    }
}

缓存文件流示例代码

#include "csv_stream.h"

using namespace capi;

csv::ocachedfstream os;
os.set_delimiter(',', "$$");
os.enable_surround_quote_on_str(true, '\"');
if (os.is_open())
{
    os << "Shampoo" << 200 << 15.0f << NEWLINE;
    os << "Towel" << 300 << 6.0f << NEWLINE;
}
os.write_to_file("products.txt");

csv::icachedfstream is("products.txt");
is.set_delimiter(',', "$$");
is.enable_trim_quote_on_str(true, '\"');

if (is.is_open())
{
    std::string name = "";
    int qty = 0;
    float price = 0.0f;
    while (is.read_line())
    {
        try
        {
            is >> name >> qty >> price;
            // display the read items
            std::cout << name << "," << qty 
                      << "," << price << std::endl;
        }
        catch (std::runtime_error& e)
        {
            std::cerr << e.what() << std::endl;
        }
    }
}

字符串流示例代码

#include "csv_stream.h"

using namespace capi;

csv::ostringstream os;
os.set_delimiter(',', "$$");
os.enable_surround_quote_on_str(true, '\"');
if (os.is_open())
{
    os << "Shampoo" << 200 << 15.0f << NEWLINE;
    os << "Towel" << 300 << 6.0f << NEWLINE;
}
os.write_to_file("products.txt");

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

if (is.is_open())
{
    std::string name = "";
    int qty = 0;
    float price = 0.0f;
    while (is.read_line())
    {
        try
        {
            is >> name >> qty >> price;
            // display the read items
            std::cout << name << "," << qty 
                      << "," << price << std::endl;
        }
        catch (std::runtime_error& e)
        {
            std::cerr << e.what() << std::endl;
        }
    }
}

输出

文件内容

"Shampoo",200,15.000000
"Towel",300,6.000000

显示输出

Shampoo,200,15
Towel,300,6

动态更改分隔符

分隔符可以在输入/输出流中通过 sep 类动态更改。 示例文本中将空格和逗号用作分隔符。

// demo sep class usage
csv::istringstream is("vt 37.8,44.32,75.1");
is.set_delimiter(' ', "$$");
csv::sep space(' ', "<space>");
csv::sep comma(',', "<comma>");
while (is.read_line())
{
    std::string type;
    float r = 0, b = 0, g = 0;
    is >> space >> type >> comma >> r >> b >> g;
    // display the read items
    std::cout << type << "|" << r << "|" << b << "|" << g << std::endl;
}

代码托管在 Github

历史

  • 2017 年 1 月 28 日: 版本 0.5.0:首次发布
  • 2017 年 2 月 19 日: 版本 0.5.1:修复读取 char 时输入流异常
  • 2017 年 3 月 12 日: 版本 0.5.2 修复了一些 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 年 9 月 18 日: 版本 0.5.3

    如果 set_delimiter() 中的转义参数为空,则带分隔符的文本将自动用引号括起来(以符合 Microsoft Excel 和一般 CSV 实践)

    "Hello,World",600

    Microsoft Excel 和 CSV 流将其读取为 "Hello,World" 和 600

  • 2018 年 8 月 12 日: 版本 0.5.4: 添加了重载的文件打开函数,该函数接受宽字符文件参数(仅在 win32 上可用)
  • 2021 年 2 月 21 日: 版本 0.5.4e: 修复了 quote_unescape 中的无限循环。
  • 2021 年 5 月 6 日: CSV 流通过换行符的存在检测行尾。 字符串输入中的换行符不可避免地会破坏解析。 新版本 0.5.5 通过转义换行符来处理它。

相关文章

© . All rights reserved.