C ++在vector <string>中加载大型txt文件的快速方法

时间:2018-10-08 09:30:05

标签: c++ vector

我有一个文件〜12.000.000十六进制行和1,6GB 文件示例:

999CBA166262923D53D3EFA72F5C4E8EE1E1FF1E7E33C42D0CE8B73604034580F2
889CBA166262923D53D3EFA72F5C4E8EE1E1FF1E7E33C42D0CE8B73604034580F2

代码示例:

vector<string>  buffer;

ifstream fe1("strings.txt");
string line1;
    while (getline(fe1, line1)) {
        buffer.push_back(line1);
    }

现在加载大约需要20分钟。有什么建议如何加快?提前谢谢。

3 个答案:

答案 0 :(得分:2)

将大型文本文件加载到std::vector<std::string>中是相当低效和浪费的,因为它会为每个std::string分配堆内存,并多次重新分配向量。这些堆分配中的每一个都需要在(normally 8 bytes per allocation on a 64-bit system)下进行堆簿记信息,并且每一行都需要一个std::string对象(8-32字节,取决于标准库),因此文件以这种方式加载在RAM中比在磁盘上占用更多的空间。

一种快速的方法是将文件映射到内存中,并实现迭代器以遍历其中的行。这回避了上述问题。

工作示例:

#include <boost/interprocess/file_mapping.hpp>
#include <boost/interprocess/mapped_region.hpp>
#include <boost/iterator/iterator_facade.hpp>
#include <boost/range/iterator_range_core.hpp>

#include <iostream>

class LineIterator
    : public boost::iterator_facade<
          LineIterator,
          boost::iterator_range<char const*>,
          boost::iterators::forward_traversal_tag,
          boost::iterator_range<char const*>
          >
{
    char const *p_, *q_;
    boost::iterator_range<char const*> dereference() const { return {p_, this->next()}; }
    bool equal(LineIterator b) const { return p_ == b.p_; }
    void increment() { p_ = this->next(); }
    char const* next() const { auto p = std::find(p_, q_, '\n'); return p + (p != q_); }
    friend class boost::iterator_core_access;

public:
    LineIterator(char const* begin, char const* end) : p_(begin), q_(end) {}
};

inline boost::iterator_range<LineIterator> crange(boost::interprocess::mapped_region const& r) {
    auto p = static_cast<char const*>(r.get_address());
    auto q = p + r.get_size();
    return {LineIterator{p, q}, LineIterator{q, q}};
}

inline std::ostream& operator<<(std::ostream& s, boost::iterator_range<char const*> const& line) {
    return s.write(line.begin(), line.size());
}

int main() {
    boost::interprocess::file_mapping file("/usr/include/gnu-versions.h", boost::interprocess::read_only);
    boost::interprocess::mapped_region memory(file, boost::interprocess::read_only);

    unsigned n = 0;
    for(auto line : crange(memory))
        std::cout << n++ << ' ' << line;
}

答案 1 :(得分:1)

您可以将整个文件读入内存。可以使用C ++流完成此操作,也可以通过使用平台特定的API(例如内存映射文件或它们自己的文件读取API)来获得更高的性能。

拥有此数据块后,为了提高性能,您希望避免再复制并就地使用。在C ++ 17中,您有std::string_view,它与std::string类似,但是使用现有的字符串数据,从而避免了复制。否则,您可能只使用C样式的char*字符串,可以通过使用一对指针(开头/结尾)或指针和大小来将换行符替换为空(\0)。

在这里我使用了string_view,我还假定换行符总是\n,并且末尾有换行符。如果不是这种情况,则可能需要调整循环。猜测vector的大小也会获得一些性能,您可以通过文件长度来实现。我还跳过了一些错误处理。

std::fstream is("data.txt", std::ios::in | std::ios::binary);
is.seekg(0, std::ios::end);
size_t data_size = is.tellg();
is.seekg(0, std::ios::beg);
std::unique_ptr<char[]> data(new char[data_size]);
is.read(data.get(), data_size);


std::vector<std::string_view> strings;
strings.reserve(data_size / 40); // If you have some idea, avoid re-allocations as general practice with vector etc.
for (size_t i = 0, start = 0; i < data_size; ++i)
{
    if (data[i] == '\n') // End of line, got string
    {
        strings.emplace_back(data.get() + start, i - start);
        start = i + 1;
    }
}

要获得更多性能,您可以运行循环以使CPU与文件IO并行进行。这可以通过线程或特定于平台的异步文件IO来完成。但是,在这种情况下,循环将非常快,因此不会有太多收获。

答案 2 :(得分:0)

您可以简单地分配足够的RAM内存并几乎一次读取整个文本文件。比起您可以通过内存指针访问RAM中的数据。我在大约3秒钟内阅读了整个4GB的文本文件。