234

std::stringファイルを に読み込む、つまりファイル全体を一度に読み込むにはどうすればよいですか?

テキスト モードまたはバイナリ モードは、呼び出し元が指定する必要があります。ソリューションは、標準に準拠し、移植可能で効率的でなければなりません。文字列のデータを不必要にコピーしてはならず、文字列の読み取り中にメモリの再割り当てを回避する必要があります。

これを行う 1 つの方法は、ファイルサイズを統計し、サイズを変更しstd::stringfread()std::stringconst_cast<char*>()'edにすることdata()です。これには、std::stringのデータが連続している必要がありますが、これは標準では要求されていませんが、既知のすべての実装に当てはまるようです。さらに悪いことに、ファイルをテキスト モードで読み取ると、std::stringのサイズがファイルのサイズと等しくない場合があります。

を に、そこから に を使用して、完全に正しく、標準に準拠し、移植可能なソリューションを構築できstd::ifstreamます。ただし、これにより、文字列データがコピーされたり、不必要にメモリが再割り当てされたりする可能性があります。rdbuf()std::ostringstreamstd::string

  • 関連するすべての標準ライブラリの実装は、不要なオーバーヘッドをすべて回避するのに十分スマートですか?
  • それを行う別の方法はありますか?
  • 必要な機能を既に提供している隠れたブースト機能を見逃していませんか?


void slurp(std::string& data, bool is_binary)
4

21 に答える 21

158

1 つの方法は、ストリーム バッファーを別のメモリ ストリームにフラッシュし、それを次のように変換することですstd::string(エラー処理は省略します)。

std::string slurp(std::ifstream& in) {
    std::ostringstream sstr;
    sstr << in.rdbuf();
    return sstr.str();
}

これは見事に簡潔です。ただし、質問で述べたように、これは冗長コピーを実行し、残念ながら基本的にこのコピーを除外する方法はありません。

残念ながら、冗長なコピーを回避する唯一の現実的な解決策は、ループ内で手動で読み取りを行うことです。C++ では連続した文字列が保証されるようになったため、次のように記述できます (≥C++17、エラー処理を含む)。

auto read_file(std::string_view path) -> std::string {
    constexpr auto read_size = std::size_t(4096);
    auto stream = std::ifstream(path.data());
    stream.exceptions(std::ios_base::badbit);
    
    auto out = std::string();
    auto buf = std::string(read_size, '\0');
    while (stream.read(& buf[0], read_size)) {
        out.append(buf, 0, stream.gcount());
    }
    out.append(buf, 0, stream.gcount());
    return out;
}
于 2008-09-22T17:22:30.033 に答える
73

最短のバリアント:Live On Coliru

std::string str(std::istreambuf_iterator<char>{ifs}, {});

ヘッダーが必要<iterator>です。

この方法は、文字列を事前に割り当てて を使用するよりも遅いという報告がいくつかありましたstd::istream::read。ただし、最適化が有効になっている最新のコンパイラでは、これはもはや当てはまらないようですが、さまざまなメソッドの相対的なパフォーマンスはコンパイラに大きく依存しているようです。

于 2008-09-22T17:13:40.553 に答える
55

同様の質問でこの回答を参照してください。

便宜上、CTT のソリューションを再投稿します。

string readFile2(const string &fileName)
{
    ifstream ifs(fileName.c_str(), ios::in | ios::binary | ios::ate);

    ifstream::pos_type fileSize = ifs.tellg();
    ifs.seekg(0, ios::beg);

    vector<char> bytes(fileSize);
    ifs.read(bytes.data(), fileSize);

    return string(bytes.data(), fileSize);
}

Moby Dick (1.3M) のテキストに対して平均 100 回の実行を行った場合、このソリューションは、ここに示されている他の回答よりも約 20% 速い実行時間をもたらしました。移植可能な C++ ソリューションとしては悪くありません。ファイルを mmap した結果を確認したいと思います ;)

于 2009-02-08T03:27:07.020 に答える
26

使用する

#include <iostream>
#include <sstream>
#include <fstream>

int main()
{
  std::ifstream input("file.txt");
  std::stringstream sstr;

  while(input >> sstr.rdbuf());

  std::cout << sstr.str() << std::endl;
}

または非常に近いもの。自分自身を再確認するために stdlib 参照を開いていません。

slurpはい、関数を要求どおりに記述しなかったことは理解しています。

于 2008-09-22T16:57:43.167 に答える
5

このようなものはそれほど悪くないはずです:

void slurp(std::string& data, const std::string& filename, bool is_binary)
{
    std::ios_base::openmode openmode = ios::ate | ios::in;
    if (is_binary)
        openmode |= ios::binary;
    ifstream file(filename.c_str(), openmode);
    data.clear();
    data.reserve(file.tellg());
    file.seekg(0, ios::beg);
    data.append(istreambuf_iterator<char>(file.rdbuf()), 
                istreambuf_iterator<char>());
}

ここでの利点は、最初に予約を行うため、読み込むときに文字列を大きくする必要がないことです。欠点は、文字ごとに行うことです。よりスマートなバージョンでは、読み取りバッファ全体を取得してからアンダーフローを呼び出すことができます。

于 2008-09-22T17:14:24.343 に答える
3

「std::getline」関数を使用して、区切り文字として「eof」を指定できます。ただし、結果のコードは少しあいまいです。

std::string data;
std::ifstream in( "test.txt" );
std::getline( in, data, std::string::traits_type::to_char_type( 
                  std::string::traits_type::eof() ) );
于 2008-09-22T17:16:23.357 に答える
0

そのために私が開発した最初の C++ ライブラリを使用できます。

#include "rst/files/file_utils.h"

std::filesystem::path path = ...;  // Path to a file.
rst::StatusOr<std::string> content = rst::ReadFile(path);
if (content.err()) {
  // Handle error.
}

std::cout << *content << ", " << content->size() << std::endl;
于 2021-11-05T13:28:27.963 に答える
0

これは私が使用する関数であり、何らかの理由で大きなファイル (1GB+) を処理する場合、ファイルサイズがわかっている場合、 std::ifstream::read() は std::ifstream::rdbuf() よりもはるかに高速であるため、全体「最初にファイルサイズを確認する」ことは、実際には速度の最適化です

#include <string>
#include <fstream>
#include <sstream>
std::string file_get_contents(const std::string &$filename)
{
    std::ifstream file($filename, std::ifstream::binary);
    file.exceptions(std::ifstream::failbit | std::ifstream::badbit);
    file.seekg(0, std::istream::end);
    const std::streampos ssize = file.tellg();
    if (ssize < 0)
    {
        // can't get size for some reason, fallback to slower "just read everything"
        // because i dont trust that we could seek back/fourth in the original stream,
        // im creating a new stream.
        std::ifstream file($filename, std::ifstream::binary);
        file.exceptions(std::ifstream::failbit | std::ifstream::badbit);
        std::ostringstream ss;
        ss << file.rdbuf();
        return ss.str();
    }
    file.seekg(0, std::istream::beg);
    std::string result(size_t(ssize), 0);
    file.read(&result[0], std::streamsize(ssize));
    return result;
}
于 2021-09-21T15:57:39.390 に答える
-1

std::string の const char * バッファーには書き込まないでください。決して!そうすることは大きな間違いです。

std::string 内の文字列全体の領域を予約() し、適切なサイズのファイルからチャンクをバッファに読み取り、append() します。チャンクの大きさは、入力ファイルのサイズによって異なります。私は、他のすべての移植可能な STL 準拠のメカニズムが同じことを行うと確信しています (ただし、よりきれいに見えるかもしれません)。

于 2008-09-22T17:05:37.297 に答える