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






3.69/5 (12投票s)
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 之间的差异。
澄清误解
仅使用此类并不能减少应用程序中的代码膨胀。 只有当所有其他 fstream
、stringstream
和 cout/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 通过转义换行符来处理它。