C++ 读取映射到内存的CSV文件的最简单方法?
当我在C++(11)中读取文件时,我使用以下方法将它们映射到内存中:C++ 读取映射到内存的CSV文件的最简单方法?,c++,csv,boost,io,memory-mapped-files,C++,Csv,Boost,Io,Memory Mapped Files,当我在C++(11)中读取文件时,我使用以下方法将它们映射到内存中: boost::interprocess::file_mapping* fm = new file_mapping(path, boost::interprocess::read_only); boost::interprocess::mapped_region* region = new mapped_region(*fm, boost::interprocess::read_only); char* bytes = stat
boost::interprocess::file_mapping* fm = new file_mapping(path, boost::interprocess::read_only);
boost::interprocess::mapped_region* region = new mapped_region(*fm, boost::interprocess::read_only);
char* bytes = static_cast<char*>(region->get_address());
boost::进程间::文件映射*fm=新文件映射(路径,boost::进程间::只读);
boost::进程间::映射的_区域*区域=新映射的_区域(*fm,boost::进程间::只读);
char*bytes=static_cast(region->get_address());
当我想以极快的速度逐字节读取时,这很好。但是,我已经创建了一个csv文件,我想将其映射到内存中,读取每一行并在逗号上拆分每一行
有没有一种方法可以通过对上述代码进行一些修改来实现这一点
(我正在映射到内存,因为我有大量内存,我不希望磁盘/IO流出现任何瓶颈)。只需从内存映射的字节创建一个istringstream,并使用以下方法解析它:
const std::string stringBuffer(bytes, region->get_size());
std::istringstream is(stringBuffer);
typedef boost::tokenizer< boost::escaped_list_separator<char> > Tokenizer;
std::string line;
std::vector<std::string> parsed;
while(getline(is, line))
{
Tokenizer tokenizer(line);
parsed.assign(tokenizer.begin(),tokenizer.end());
for (auto &column: parsed)
{
//
}
}
const std::string stringBuffer(字节,区域->获取大小());
std::istringstream是(stringBuffer);
typedef boost::标记器标记器;
std::字符串行;
解析向量;
while(getline(is,line))
{
标记器标记器(行);
assign(tokenizer.begin(),tokenizer.end());
用于(自动列:已解析(&C)
{
//
}
}
请注意,在许多系统上,与顺序读取相比,内存映射并没有提供任何速度优势。在这两种情况下,您最终都会一页一页地从磁盘读取数据,可能提前读取的量是相同的,并且在这两种情况下,IO延迟和带宽都是相同的。你是否有足够的记忆力不会有任何区别。此外,根据系统的不同,内存映射(即使是只读的)可能会导致令人惊讶的行为(例如,保留交换空间),这有时会让人们忙于故障排除。以下是我对“足够快”的看法。它可以在约1秒的时间内通过116个MiB的CSV(2.5Mio行[1])进行压缩
结果可以在零拷贝下随机访问,因此没有开销(除非交换页面)
作为比较:
- 这比原始的
处理同一文件快3倍wc csv.txt
- 它的速度大约与下面的perl one liner(列出所有行上的不同字段计数)一样快:
- 它的速度仅比避免使用区域设置功能的
慢(大约1.5倍)(LANG=C wc csv.txt)
using CsvField = boost::string_ref;
using CsvLine = std::vector<CsvField>;
using CsvFile = std::vector<CsvLine>; // keep it simple :)
struct CsvParser : qi::grammar<char const*, CsvFile()> {
CsvParser() : CsvParser::base_type(lines)
{
using namespace qi;
field = raw [*~char_(",\r\n")]
[ _val = construct<CsvField>(begin(_1), size(_1)) ]; // semantic action
line = field % ',';
lines = line % eol;
}
// declare: line, field, fields
};
印刷品
116 MiB parsed into 2578421 lines of CSV values
您可以像std::string
一样使用这些值:
for (int i = 0; i < 10; ++i)
{
auto l = rand() % parsed.size();
auto& line = parsed[l];
auto c = rand() % line.size();
std::cout << "Random field at L:" << l << "\t C:" << c << "\t" << line[c] << "\n";
}
完整的工作样本在这里
[1]我通过反复添加
while read a && read b && read c && read d && read e
do echo "$a,$b,$c,$d,$e"
done < /etc/dictionaries-common/words
读a&&b&&c&&d&&e时
做回显“$a、$b、$c、$d、$e”
完成
到
csv.txt
,直到它计数到250万行。所以内存映射不会预先加载文件?我以为是的!将其加载到stringstream中可确保复制所有内存first@user997112不,它不会将文件预先加载到物理内存中,这是一件好事。首先,非常大的文件会出现问题,然后它会阻止您覆盖IO和处理。正如sehe所说,即使将其加载到stringstream会将每个页面加载到内存中,也不能保证此时整个文件都在RAM中(内存页面可能会被丢弃)。我使用Boost编写了一个示例来解析整个源代码,而不是满足于速度慢和使用std::string
和std::stringstream
进行复制。这可以做得更快,但这肯定是一个好的开始。注意:你可以做得更快,但要付出不便的代价。特别是有madvise
,您可以惰性地解析(只在第一行进行解析,并根据需要进行解析)。此外,请参阅我的其他答案,以了解更通用的CSV解析(例如,允许引用值和转义)。下一步:如果你知道前面的列数,你可以避免大量的分配。哪里可以找到引用和转义?我的其他答案,例如,等等。大多数都是从一个简单的搜索中挑选出来的,比如
for (int i = 0; i < 10; ++i)
{
auto l = rand() % parsed.size();
auto& line = parsed[l];
auto c = rand() % line.size();
std::cout << "Random field at L:" << l << "\t C:" << c << "\t" << line[c] << "\n";
}
Random field at L:1979500 C:2 sateen's
Random field at L:928192 C:1 sackcloth's
Random field at L:1570275 C:4 accompanist's
Random field at L:479916 C:2 apparel's
Random field at L:767709 C:0 pinks
Random field at L:1174430 C:4 axioms
Random field at L:1209371 C:4 wants
Random field at L:2183367 C:1 Klondikes
Random field at L:2142220 C:1 Anthony
Random field at L:1680066 C:2 pines
while read a && read b && read c && read d && read e
do echo "$a,$b,$c,$d,$e"
done < /etc/dictionaries-common/words