首页 > 解决方案 > 在 C++ 中,从大型文本文件集合中读取所有单词的最快方法是什么?

问题描述

我有一个大文本文件的集合,我希望我的代码逐字读取它们并将单词存储在字符串变量中。我编写了一个简单的代码作为原型,它从文件集合中读取单词并打印它们(实际代码没有打印命令),但我的实际程序将包含大约 20 个文件和数十万个单词。看来这段代码的性能不会很好。有什么可以提高速度的吗?

#include <iostream>
#include <fstream>
using namespace std;
int main() {
    string s;
    string array[3]={"text1.txt","text2.txt","text3.txt"};
    for(int i=0;i<3;i++){
        ifstream fileread;
        fileread.open(array[i]);
        while(fileread>>s){
            cout<<s<<endl;
        }

    }
    return 0;
}

标签: c++

解决方案


您已经将单词放入字符串变量中,最有可能减慢速度的是将实际单词打印到std::cout.

如果你有一个非常快的磁盘(比如 ramdisk),理论上你可以通过一次读取多个文件来加快速度。

我已经提出了一个想法(需要 C++17 或更高版本)如何做到这一点,它还测量它所花费的时间并在读取完成时显示一些统计数据。我已经在代码中进行了注释以解释它的作用。要与串行读取文件时的速度进行比较,只需std::execution::par删除std::for_each.

#include <algorithm>
#include <chrono>
#include <cstddef>
#include <execution>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <string>
#include <string_view>
#include <unordered_map>
#include <vector>

int cppmain(const std::string_view program, std::vector<std::string> files) {
    if(files.empty()) {
        std::cerr << "USAGE: " << program << " <files...>\n";
        return 1;
    }

    // measure how long time it takes to read all the files
    auto start = std::chrono::steady_clock::now();

    // a map of filenames mapping to a vector of words in each file
    std::unordered_map<std::string, std::vector<std::string>> file_words_map;

    // create the filename Keys in the map - this is needed to not insert keys
    // in the map object itself from multiple threads simultaneously.
    for(auto& file : files) file_words_map[file];

    // make sure the files vector only contains unique filenames
    if(files.size() != file_words_map.size()) {
        // we had duplicate filenames - remove them
        files.resize(file_words_map.size());
        std::transform(file_words_map.begin(), file_words_map.end(),
                       files.begin(),
                       [](auto& file_words) { return file_words.first; });
    }

    // read from multiple files simultaneously
    std::for_each(std::execution::par, files.begin(), files.end(),
                  [&file_words_map](auto& file) {                      
                      std::ifstream is(file); // open a file for reading
                      if(is) {                // and check that it opened ok
                          // get a reference to this file's vector in the map
                          auto& words = file_words_map[file];

                          // copy all words from the file into the words vector
                          std::copy(std::istream_iterator<std::string>(is),
                                    std::istream_iterator<std::string>{},
                                    std::back_inserter(words));
                      }
                  });

    // all files read, calculate how long time it took
    auto elapsed_time = std::chrono::steady_clock::now() - start;

    // calculate how many words we read in total
    auto total =
        std::accumulate(file_words_map.begin(), file_words_map.end(), 0ULL,
                        [](const auto val, const auto& file_words) {
                            return val + file_words.second.size();
                        });

    // statistics
    constexpr std::size_t max_words_to_display = 5;

    for(const auto& [file, words] : file_words_map) {
        std::cout << std::setw(50) << std::left << file << ' ' << std::setw(10)
                  << std::right << words.size() << "\n ";

        std::copy_n(words.begin(), std::min(max_words_to_display, words.size()),
                    std::ostream_iterator<std::string>(std::cout, ", "));

        if(words.size() > max_words_to_display) std::cout << "...";
        std::cout << '\n';
    }

    std::cout << "Read " << total << " words from " << file_words_map.size()
              << " files in "
              << std::chrono::duration_cast<std::chrono::milliseconds>(elapsed_time)
                                                                      .count()
              << " ms.\n";

    return 0;
}

int main(int argc, char* argv[]) {
    return cppmain(argv[0], {argv + 1, argv + argc});
}

示例输出:

...
handler.cpp                                               155
 #include, <condition_variable>, #include, <functional>, #include, ...
pybind.cpp                                                162
 #include, <iostream>, #include, <pybind11.h>, #include, ...
readip.cpp                                                296
 #include, <netinet/ip.h>, #include, <fstream>, #include, ...
realdist.cpp                                              299
 #include, <cmath>, #include, <iostream>, #include, ...
strerr.cpp                                                125
 #include, <iostream>, using, namespace, std;, ...
rec.cpp                                                    91
 #include, <filesystem>, #include, <iostream>, #include, ...
Read 239715 words from 1621 files in 11 ms.

注意:您实际上可以直接执行for_each循环file_words_map而不是使用files向量,然后words像上面的代码那样在地图中查找向量 - 这会更干净,并且不需要从向量中删除重复项 -但对我来说,这种方法要快 4-5 倍,因为速度很重要,所以我使用了这个。您可以自己进行试验以查看结果。

如果您使用g++or clang++,请编译:

-O3 -std=c++17 -ltbb -pthread

在 MSVC 中,您不需要指定库。


推荐阅读