Я хочу обработать большой текстовый файл построчно. Я нашел код, который выглядит очень быстрым при чтении файла:
std::vector<std::byte> load_file(std::string const& filepath)
{
std::ifstream ifs(filepath, std::ios::binary|std::ios::ate);
if (!ifs)
throw std::runtime_error(filepath + ": " + std::strerror(errno));
auto end = ifs.tellg();
ifs.seekg(0, std::ios::beg);
auto size = std::size_t(end - ifs.tellg());
if (size == 0) // avoid undefined behavior
return {};
std::vector<std::byte> buffer(size);
if (!ifs.read((char*)buffer.data(), buffer.size()))
throw std::runtime_error(filepath + ": " + std::strerror(errno));
return buffer;
}
Теперь проблема в том, что я не знаю, как использовать это для чтения строк файла.
Это решение, которое я придумал... но оно выглядит очень плохо или неэффективно! Есть ли лучший способ сделать это, или функция load_file
просто не предназначена для такой работы, как чтение строк из текстового файла?
auto fileConent = load_file(R"(C:\analysis\simple.txt)");
auto line = vector<std::byte>();
for(const auto& byte : fileConent) {
if (static_cast<char>(byte) != '\n') {
line.push_back(byte);
} else {
std::cout
<< std::string_view(reinterpret_cast<char*>(line.data()), line.size())
<< std::endl;
line.clear();
}
}
Если это текстовый файл (что подразумевается, поскольку вы говорите о строках), то зачем открывать его в двоичном режиме и хранить в std::vector<std::byte>
? Вы можете просто сохранить файл в std::vector<std::string>
не используйте код, который вы где-то нашли, если вы не понимаете, для чего он был написан.
вопрос, который вы должны задать: «Как читать строки из текстового файла?»
кстати std::cout <<
медленно. Вам не нужен самый эффективный способ чтения из файла, когда вы все равно пишете в stdout
. Запись в stdout
медленная, но все же намного быстрее, чем любой человек может прочитать
Сколько гигабайт "большой"?
Поскольку вы, очевидно, счастливы использовать std::string_view
, то почему бы не прочитать весь файл в std::string
вместо std::vector
, а затем создать std::string_view
для каждой строки, ссылающейся на std::string
, содержащую весь файл?
@john хорошо, я бы сделал это, если бы знал как :D
@DEKKER Что ж, в вашей функции load_file
вы можете буквально заменить std::vector<std::byte>
на std::string
.
@DEKKER А в оставшемся коде нужно просто заменить цикл, основанный на диапазоне, на обычный цикл for. Все, что вам нужно, чтобы сделать std::string_view
, это указатель на начало строки и длину строки. Это не должно быть слишком сложно получить.
Самый простой способ — передать буфер std::istringstream
и прочитать из него строки вместо того, чтобы реализовывать его самостоятельно.
Конечно, вы также можете пропустить функцию и читать строки из std::ifstream
напрямую.
Если есть ссылка на термин fast, то вы часто будете читать комментарии типа:
Итак, во-первых, убедитесь, что вы скомпилировали свою программу со всеми оптимизациями скорости. Тогда, пожалуйста, поймите, 1 000 000 строк в наши дни считаются маленькими.
Относительно показанного исходного кода:
Ваш первый пример кода просто считывает файл в std::vector
из std::bytes
. Он делает это, пытаясь прочитать размер файла, а затем используя очень быструю read
-функцию std::ifstream
.
Это сработает и будет быстро, но вам не поможет, потому что вам нужны линии.
Второй фрагмент кода анализирует std::vector
, который был прочитан ранее, и печатает строку после каждого найденного '\n'. Это в принципе нормально. Но std::string_view
не сохраняются. Возможно, этого решения вам достаточно.
Во всяком случае, есть некоторые комментарии.
file_size
-функцию из <filesystem>
pubsetbuf
-функции std::ifstream
s streambuf
.Использование неоптимизированных потоковых функций, таких как std::getline
или std::istringstream
, не поможет. Это будет намного медленнее.
Чтобы показать вам, чего можно достичь, я создал тестовый файл с 50 000 000 строк. В результате размер для моего теста составил 1,5 ГБ.
См. пример ниже:
#include <iostream>
#include <fstream>
#include <chrono>
#include <filesystem>
#include <random>
#include <string_view>
struct Timer {
std::chrono::time_point<std::chrono::high_resolution_clock> startTime{};
long long elapsedTime{};
void start() { startTime = std::chrono::high_resolution_clock::now(); }
void stop() { elapsedTime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - startTime).count(); }
friend std::ostream& operator << (std::ostream& os, const Timer& t) { return os << t.elapsedTime << " ms "; }
};
constexpr size_t NumberOfRows = 50'000'000U;
constexpr size_t NumberOfRowsGuess = 60'000'000U;
constexpr int MinLineLength = 10;
constexpr int MaxLineLength = 30;
const std::string testDataFileName{ "r:\\test.txt" };
void createTestFile() {
static std::random_device rd{};
static std::mt19937 gen{ rd() };
std::uniform_int_distribution<unsigned int> uniformDistributionStringLength(MinLineLength, MaxLineLength);
if (std::ofstream testDataStream(testDataFileName); testDataStream) {
Timer t1; t1.start();
for (size_t row{}; row < NumberOfRows; ++row) {
testDataStream << row << ' ' << std::string(uniformDistributionStringLength(gen), 'a') << '\n';
}
t1.stop(); std::cout << "\nDuration for test file creation: " << t1 << '\n';
}
else std::cerr << "\nError: Could not open file '" << testDataFileName << "' for writing.\n\n";
}
constexpr std::size_t IOBufSize = 5'000'000u;
static char ioBuf[IOBufSize];
int main() {
//createTestFile();
if (std::ifstream ifs{ testDataFileName,std::ios::binary }; ifs) {
Timer tOverall{}; tOverall.start();
// To speed up reading of the file, we will set a bigger input buffer
ifs.rdbuf()->pubsetbuf(ioBuf, IOBufSize);
// Here we will store the complete file, all data
std::string text{};
// Get number of bytes in file
const std::uintmax_t size = std::filesystem::file_size(testDataFileName);
text.resize(size);
// Read the whole file with one statement. Will be ultrafast
Timer t; t.start();
ifs.read(text.data(), size);
t.stop(); std::cout << "Duration for reading complete file:\t\t" << t << "\t\tData read: " << ifs.gcount() << " bytes\n";
// Creating a vector with string views and reserve memory. Make a big guess
std::vector<std::string_view> lines{};
lines.reserve(NumberOfRowsGuess);
// Create the string views with the lines
char* start{ text.data() };
char* end{start};
std::size_t index{};
t.start();
for (const char c : text) {
++end;
if (c == '\n') {
lines.push_back({start, end });
start = end;
}
}
std::cout << "\nNumber of lines Read: " << lines.size() << '\n';
t.stop(); std::cout << "Duration for creating all string views:\t\t" << t << '\n';
tOverall.stop(); std::cout << "\n\nDuration overall:\t\t\t\t" << tOverall << '\n';
}
else std::cout << "\n\nError: Could not open test file '" << testDataFileName << "'\n\n";
}
Я протестировал программу на своей 12-летней машине с Windows 7.
Вывод программы:
Duration for reading complete file: 752 ms Data read: 1538880087 bytes
Number of lines Read: 50000000
Duration for creating all string views: 1769 ms
Duration overall: 2966 ms
Это должно быть "достаточно" быстро.
std::getline
?