Как преобразовать вектор байтов в строки и сохранить в виде строки

Я хочу обработать большой текстовый файл построчно. Я нашел код, который выглядит очень быстрым при чтении файла:

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::getline?
Alan Birtles 18.04.2023 14:54

Если это текстовый файл (что подразумевается, поскольку вы говорите о строках), то зачем открывать его в двоичном режиме и хранить в std::vector<std::byte>? Вы можете просто сохранить файл в std::vector<std::string>

Ted Lyngmo 18.04.2023 14:59

не используйте код, который вы где-то нашли, если вы не понимаете, для чего он был написан.

463035818_is_not_a_number 18.04.2023 14:59

вопрос, который вы должны задать: «Как читать строки из текстового файла?»

463035818_is_not_a_number 18.04.2023 15:01

кстати std::cout << медленно. Вам не нужен самый эффективный способ чтения из файла, когда вы все равно пишете в stdout. Запись в stdout медленная, но все же намного быстрее, чем любой человек может прочитать

463035818_is_not_a_number 18.04.2023 15:03

Сколько гигабайт "большой"?

molbdnilo 18.04.2023 15:06

Поскольку вы, очевидно, счастливы использовать std::string_view, то почему бы не прочитать весь файл в std::string вместо std::vector, а затем создать std::string_view для каждой строки, ссылающейся на std::string, содержащую весь файл?

john 18.04.2023 15:33

@john хорошо, я бы сделал это, если бы знал как :D

DEKKER 18.04.2023 16:18

@DEKKER Что ж, в вашей функции load_file вы можете буквально заменить std::vector<std::byte> на std::string.

john 18.04.2023 16:28

@DEKKER А в оставшемся коде нужно просто заменить цикл, основанный на диапазоне, на обычный цикл for. Все, что вам нужно, чтобы сделать std::string_view, это указатель на начало строки и длину строки. Это не должно быть слишком сложно получить.

john 18.04.2023 16:30
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
10
79
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Самый простой способ — передать буфер 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::ifstreams 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

Это должно быть "достаточно" быстро.

Другие вопросы по теме