C++ 读取映射到内存的CSV文件的最简单方法?

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

当我在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 = 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])进行压缩

结果可以在零拷贝下随机访问,因此没有开销(除非交换页面)

作为比较:

  • 这比原始的
    wc csv.txt
    处理同一文件快3倍
  • 它的速度大约与下面的perl one liner(列出所有行上的不同字段计数)一样快:

  • 它的速度仅比避免使用区域设置功能的
    (LANG=C wc csv.txt)
    慢(大约1.5倍)

下面是解析器的全部荣耀:

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