C ++でファイル全体をstd :: stringに読み込むにはどうすればよいですか?


178

ファイルをに読み込む方法std::string、つまり、ファイル全体を一度に読み込む方法は?

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

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

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

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


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

まだ十分に規定されていないものがあることに注意してください。たとえば、ファイルの文字エンコーディングは何ですか?自動検出を試みますか(これは特定のケースでのみ機能します)?ファイルのエンコーディングを伝えるXMLヘッダーなどを尊重しますか?また、「テキストモード」や「バイナリモード」などはありません-FTPを考えていますか?
Jason Cohen

テキストモードとバイナリモードは、MSDOSおよびWindows固有のハックであり、改行がWindowsで2文字(CR / LF)で表されるという事実を回避しようとします。テキストモードでは、1つの文字( '\ n')として扱われます。
Ferruccio

1
完全に重複しているわけではありませんが(かなり)、これは密接に関連しています:std :: stringオブジェクトにメモリを事前に割り当てる方法?(上記のKonradのステートメントとは異なり、これには、追加のコピーを行わずにファイルを宛先に直接読み取るコードが含まれていました)。
Jerry Coffin 2012

1
「標準では隣接は必須ではありません」-はい、そうです。文字列でop []を使用するとすぐに、連続した書き込み可能なバッファに合体する必要があるため、最初に.resize()が十分に大きければ、&str [0]に安全に書き込むことが保証されます。また、C ++ 11では、文字列は常に連続しています。
Tino Didriksen 2013

2
関連リンク:C ++でファイルを読み取る方法 -さまざまなアプローチのベンチマークと議論。そして、はい、rdbuf(受け入れられた答えの1つが)最速でreadはありません。
legends2k 2014年

回答:


138

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

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

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

冗長なコピーを回避する唯一の実際の解決策は、残念ながらループで手動で読み取ることです。C ++は連続した文字列を保証しているので、次のように書くことができます(≥C++ 14):

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;
}

20
ワンライナーにする意味は?私は常に読みやすいコードを選びます。自称VB.Net愛好家(IIRC)として、私はあなたが感情を理解する必要があると思いますか?
sehe

5
@sehe:中途半端な能力のあるC ++コーダーなら、そのワンライナーをすぐに理解できると思います。周りにある他のものと比べると、かなり使いこなされています。
DevSolar 2012

43
@DevSolarさて、より読みやすいバージョンは〜30%短く、キャストがなく、他の点では同等です。したがって、私の質問は「それをワンライナーにする意味は何ですか?」
sehe

13
注:このメソッドは、ファイルをstringstreamのバッファーに読み込み、そのバッファー全体をにコピーしstringます。つまり、他のオプションの2倍のメモリが必要です。(バッファを移動する方法はありません)。大きなファイルの場合、これは重大なペナルティとなり、割り当ての失敗を引き起こす可能性もあります。
MM

9
@DanNissenbaumあなたは何かを混乱させています。簡潔さはプログラミングにおいて確かに重要ですが、それを達成する適切な方法は、問題をパーツに分解し、それらを独立したユニット(関数、クラスなど)にカプセル化することです。関数を追加しても簡潔さは損なわれません。全く逆です。
Konrad Rudolph

52

この回答を見る同様の質問でを。

参考までに、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した結果を確認したい;)


3
関連:さまざまなメソッドの時間パフォーマンスの比較:C ++でファイル全体を一度に
読み取る

12
今日まで、私はtellg()がファイルサイズ以外の結果を報告するのを見たことはありません。バグの原因を見つけるのに何時間もかかった。ファイルサイズを取得するためにtellg()を使用しないでください。stackoverflow.com/questions/22984956/...
Puzomorクロアチア

ifs.seekg(0, ios::end)前に電話してはいけませんtellgか?ファイルを開いた直後はポインタが先頭にあるためtellgゼロを返します
Andriy Tylychko

1
また、あなたは間接参照うとして空のファイルをチェックするために必要なnullptrことで&bytes[0]
アンドリーTylychko

ios::ateわかりました。見逃しました。最後に明示的に移動したバージョンの方が読みやすいと思います
Andriy Tylychko

50

最短のバリエーション: Live On Coliru

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

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

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


7
この答えを説明していただけませんか。それはどれほど効率的ですか、とにかく攪拌メモリを事前に割り当てるために、一度に1文字ずつファイルを読み取りますか?
マーティン・ベケット

@MM私がその比較を読む方法では、このメソッドは、純粋なC ++が事前に割り当てられたバッファへの読み込みメソッドよりも遅いです。
Konrad Rudolph

そのとおりです。タイトルがコードサンプルの下ではなく、コードサンプルの下にある場合です:)
MM

@juzzlin C ++はそのようには機能しません。特定の環境でヘッダーを必要としないことは、ヘッダーを含めない理由として適切ではありません。
LF

このメソッドはメモリの再割り当てを何度もトリガーしますか?
コインチャン

22

使用する

#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求められたとおりに関数を記述しなかったことを理解しています。


これは見栄えは良いですが、コンパイルされません。コンパイルできるように変更すると、このページの他の回答に減ります。 ideone.com/EyhfWm
JDiMatteo

5
なぜwhileループ?
Zitrax '19年

同意した。にoperator>>読み込むとstd::basic_streambuf、入力ストリーム(残りのもの)が消費されるため、ループは不要です。
レミールボー・

15

C ++ 17(std :: filesystem)を使用している場合は、次の方法もあります(andのstd::filesystem::file_size代わりにファイルのサイズを取得します)。seekgtellg

#include <filesystem>
#include <fstream>
#include <string>

namespace fs = std::filesystem;

std::string readFile(fs::path path)
{
    // Open the stream to 'lock' the file.
    std::ifstream f(path, std::ios::in | std::ios::binary);

    // Obtain the size of the file.
    const auto sz = fs::file_size(path);

    // Create a buffer.
    std::string result(sz, '\0');

    // Read the whole file into the buffer.
    f.read(result.data(), sz);

    return result;
}

:あなたが使用する必要があります<experimental/filesystem>し、std::experimental::filesystemあなたの標準ライブラリがまだ完全にC ++ 17をサポートしていない場合。また、交換する必要があるかもしれませんresult.data()&result[0]、それはサポートしていない場合は非constのstd ::のbasic_stringデータを


1
これにより、未定義の動作が発生する可能性があります。テキストモードでファイルを開くと、一部のオペレーティングシステムのディスクファイルとは異なるストリームが生成されます。
MM

1
もともとboost::filesystemはc ++ 17がない場合でもブーストを使用できるように開発されました
Gerhard Burger

2
あるAPIでファイルを開き、別のAPIでそのサイズを取得すると、不整合と競合状態が要求されるようです。
Arthur Tacca

14

を使用して回答に直接コメントするのに十分な評判がありません tellg()

tellg()エラーの場合は-1を返す可能性があることに注意してください。の結果を渡す場合tellg()割り当てパラメーターとして、最初に結果を正常性チェックする必要があります。

問題の例:

...
std::streamsize size = file.tellg();
std::vector<char> buffer(size);
...

上記の例でtellg()は、エラーが発生した場合は-1を返します。符号付き(つまりの結果tellg())と符号なし(つまり、vector<char>コンストラクターへの引数)の間で暗黙的にキャストすると、非常に多くのバイトがベクトルに誤って割り当てられます。(おそらく4294967295バイト、つまり4GB)。

上記を考慮してpaxos1977の回答を変更します。

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

    ifstream::pos_type fileSize = ifs.tellg();
    if (fileSize < 0)                             <--- ADDED
        return std::string();                     <--- ADDED

    ifs.seekg(0, ios::beg);

    vector<char> bytes(fileSize);
    ifs.read(&bytes[0], fileSize);

    return string(&bytes[0], fileSize);
}

5

このソリューションは、rdbuf()ベースのメソッドにエラーチェックを追加します。

std::string file_to_string(const std::string& file_name)
{
    std::ifstream file_stream{file_name};

    if (file_stream.fail())
    {
        // Error opening file.
    }

    std::ostringstream str_stream{};
    file_stream >> str_stream.rdbuf();  // NOT str_stream << file_stream.rdbuf()

    if (file_stream.fail() && !file_stream.eof())
    {
        // Error reading file.
    }

    return str_stream.str();
}

元のメソッドにエラーチェックを追加することは、予想したほど簡単ではないため、この回答を追加します。元のメソッドはstringstreamの挿入演算子(str_stream << file_stream.rdbuf())を使用します。問題は、文字が挿入されていないときに文字列ストリームのフェイルビットを設定することです。エラーが原因であるか、ファイルが空であることが原因である可能性があります。failbitを検査して障害をチェックすると、空のファイルを読み取るときに誤検知が発生します。ファイルが空であるために文字を挿入する正当な失敗と文字を挿入する「失敗」を明確にする方法は?

空のファイルを明示的にチェックすることを考えるかもしれませんが、それはより多くのコードと関連するエラーチェックです。

str_stream.fail() && !str_stream.eof()挿入操作ではeofbitが設定されないため(ostringstreamまたはifstreamで)、障害状態のチェックは機能しません。

したがって、解決策は操作を変更することです。ostringstreamの挿入演算子(<<)を使用する代わりに、eofbitを設定するifstreamの抽出演算子(>>)を使用します。次に、障害状態を確認しますfile_stream.fail() && !file_stream.eof()

重要なのfile_stream >> str_stream.rdbuf()は、正当な障害に遭遇したときに、eofbitを設定してはならないことです(仕様についての私の理解によれば)。つまり、上記のチェックで正当な障害を検出するには十分です。


3

このようなものは悪くないはずです:

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>());
}

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


1
文字列ではなくstd :: vectorを最初の読み取りに使用するこのコードのバージョンをチェックアウトする必要があります。はるかに高速です。
paxos1977 2009

3

これは、適度に堅牢なエラーチェックを備えた新しいファイルシステムライブラリを使用したバージョンです。

#include <cstdint>
#include <exception>
#include <filesystem>
#include <fstream>
#include <sstream>
#include <string>

namespace fs = std::filesystem;

std::string loadFile(const char *const name);
std::string loadFile(const std::string &name);

std::string loadFile(const char *const name) {
  fs::path filepath(fs::absolute(fs::path(name)));

  std::uintmax_t fsize;

  if (fs::exists(filepath)) {
    fsize = fs::file_size(filepath);
  } else {
    throw(std::invalid_argument("File not found: " + filepath.string()));
  }

  std::ifstream infile;
  infile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
  try {
    infile.open(filepath.c_str(), std::ios::in | std::ifstream::binary);
  } catch (...) {
    std::throw_with_nested(std::runtime_error("Can't open input file " + filepath.string()));
  }

  std::string fileStr;

  try {
    fileStr.resize(fsize);
  } catch (...) {
    std::stringstream err;
    err << "Can't resize to " << fsize << " bytes";
    std::throw_with_nested(std::runtime_error(err.str()));
  }

  infile.read(fileStr.data(), fsize);
  infile.close();

  return fileStr;
}

std::string loadFile(const std::string &name) { return loadFile(name.c_str()); };

infile.openstd::string変換せずに受け入れることもできます.c_str()
Matt Eding

filepathは、std::stringではありませんstd::filesystem::path。判明したstd::ifstream::openとしても、それらのいずれかを受け入れることができます。
David G

@DavidG std::filesystem::pathは暗黙的に変換可能std::string
ジェフリーキャッシュ

cppreference.comによれば、::open上のメンバ関数std::ifstream受け入れることがstd::filesystem::pathあるかのように動作する::c_str()方法は、パスに呼ばれました。::value_typeパスの基礎となるのはcharPOSIXです。
David G

2

'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() ) );

5
私はこれをテストしましたが、ファイルサイズを取得し、ファイルサイズ全体をバッファーに読み取るために呼び出すよりもはるかに遅いようです。約12倍遅い。
デビッド

これは、ファイルに "eof"(たとえば0x00、0xffなど)の文字が含まれていない場合にのみ機能します。ある場合は、ファイルの一部のみを読み取ります。
Olaf Dietsche 2017

2

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

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


5
C ++ 11以降、std::stringバッファに直接書き込むことが問題ないことが保証されています。そして、それ以前の実際のすべての実装で正しく機能したと思います
MM

1
C ++ 17以降、のstd::string::data()ようなトリックに頼らずに文字列バッファを直接変更する非const メソッドさえあり&str[0]ます。
zett42 2018年

@ zett42に同意すると、この答えは実際には正しくありません
jeremyong

0
#include <string>
#include <sstream>

using namespace std;

string GetStreamAsString(const istream& in)
{
    stringstream out;
    out << in.rdbuf();
    return out.str();
}

string GetFileAsString(static string& filePath)
{
    ifstream stream;
    try
    {
        // Set to throw on failure
        stream.exceptions(fstream::failbit | fstream::badbit);
        stream.open(filePath);
    }
    catch (system_error& error)
    {
        cerr << "Failed to open '" << filePath << "'\n" << error.code().message() << endl;
        return "Open fail";
    }

    return GetStreamAsString(stream);
}

使用法:

const string logAsString = GetFileAsString(logFilePath);

0

CTTのソリューションに基づく更新された機能:

#include <string>
#include <fstream>
#include <limits>
#include <string_view>
std::string readfile(const std::string_view path, bool binaryMode = true)
{
    std::ios::openmode openmode = std::ios::in;
    if(binaryMode)
    {
        openmode |= std::ios::binary;
    }
    std::ifstream ifs(path.data(), openmode);
    ifs.ignore(std::numeric_limits<std::streamsize>::max());
    std::string data(ifs.gcount(), 0);
    ifs.seekg(0);
    ifs.read(data.data(), data.size());
    return data;
}

2つの重要な違いがあります。

tellg()ファイルの先頭からのオフセットをバイト単位で返すことは保証されていません。代わりに、Puzomor Croatiaが指摘したように、fstream呼び出し内で使用できるトークンのようなものです。gcount()しかし抽出された最後のバイト未フォーマットの量を返します。したがって、ファイルを開き、すべてのコンテンツを抽出して破棄しignore()、ファイルのサイズを取得して、それに基づいて出力文字列を作成します。

次に、文字列に直接書き込むことで、ファイルのデータstd::vector<char>をa からa にコピーする必要がstd::stringなくなります。

パフォーマンスの面では、これは絶対に最速であり、事前に適切なサイズの文字列を割り当てて、read()1回呼び出す必要があります。興味深い事実として、使用ignore()してcountg()の代わりに、atetellg()GCCには、までコンパイルさほとんど同じ事、少しずつ。


1
このコードは機能しません。空の文字列を取得しています。私はあなたifs.seekg(0)ifs.clear()(それでうまくいく)の代わりに欲しかったと思います。
Xeverous

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.