C ++에서 전체 파일을 std :: string으로 읽는 방법은 무엇입니까?


178

파일을로 읽는 방법 std::string, 즉 전체 파일을 한 번에 읽는 방법은 무엇입니까?

텍스트 또는 이진 모드는 호출자가 지정해야합니다. 솔루션은 표준을 준수하고 휴대 가능하며 효율적이어야합니다. 문자열 데이터를 불필요하게 복사해서는 안되며 문자열을 읽는 동안 메모리 재 할당을 피해야합니다.

의 파일 크기를 찍으하는 것이 작업을 수행하는 한 가지 방법은 크기를 조정 std::string하고 fread()std::string'의 const_cast<char*>()'에드 data(). 이를 위해서는 std::string의 데이터가 표준에 필요하지 않은 연속적이어야하지만 모든 알려진 구현의 경우 인 것 같습니다. 더 나쁜 것은, 파일을 텍스트 모드에서 읽는 경우 std::string의 크기가 파일의 크기와 같지 않을 수 있습니다.

완전히 올바른, 표준 준수 및 휴대용 솔루션을 사용하여 구성 할 수 std::ifstream의를 rdbuf()std::ostringstream와에 거기에서 std::string. 그러나 이것은 문자열 데이터를 복사하거나 메모리를 불필요하게 재 할당 할 수 있습니다.

  • 모든 관련 표준 라이브러리 구현이 불필요한 오버 헤드를 피할 수있을만큼 똑똑합니까?
  • 다른 방법이 있습니까?
  • 이미 원하는 기능을 제공하는 숨겨진 부스트 기능이 누락 되었습니까?


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

아직 지정되지 않은 사항이 있습니다. 예를 들어, 파일의 문자 인코딩은 무엇입니까? 자동 감지를 시도합니까 (특정 경우에만 작동)? 예를 들어 파일의 인코딩을 알려주는 XML 헤더를 존중 하시겠습니까? 또한 "텍스트 모드"또는 "이진 모드"와 같은 것은 없습니다. FTP를 생각하고 있습니까?
Jason Cohen

텍스트 및 이진 모드는 줄 바꿈이 Windows에서 두 문자 (CR / LF)로 표시된다는 사실을 해결하려는 MSDOS 및 Windows 전용 해킹입니다. 텍스트 모드에서는 하나의 문자 ( '\ n')로 취급됩니다.
Ferruccio

1
정확히 중복되지는 않지만 std :: string 객체의 메모리를 미리 할당하는 방법 과 밀접한 관련이 있습니다. (위의 Konrad의 진술과 달리, 추가 사본을 수행하지 않고 파일을 대상으로 직접 읽는 코드를 포함했습니다).
Jerry Coffin

1
"표준에 인접하지 않아도됩니다"-예, 원형 교차로입니다. 문자열에서 op []를 사용하자마자 연속적인 쓰기 가능한 버퍼로 병합되어야하므로 .resize ()를 먼저 충분히 크게 사용하면 & str [0]에 쓰는 것이 안전합니다. 그리고 C ++ 11에서 문자열은 항상 연속적입니다.
티노 Didriksen

2
관련 링크 : C ++에서 파일을 읽는 방법? -다양한 접근법을 벤치마킹하고 논의합니다. 그리고 그렇습니다 rdbuf(허용 된 답변 중 하나)는 가장 빠르지 않습니다 read.
legends2k

답변:


138

한 가지 방법은 스트림 버퍼를 별도의 메모리 스트림으로 플러시하고 다음으로 변환하는 것입니다 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

43
@DevSolar 글쎄, 더 읽기 쉬운 버전은 ~ 30 % 더 짧고, 캐스트가없고, 다른 방식으로 동등합니다. 그러므로 제 질문은 "얼라 인라이너로 만드는 것이 무엇입니까?"입니다.
sehe

13
참고 :이 방법은 파일을 문자열 스트림의 버퍼로 읽은 다음 해당 전체 버퍼를에 복사합니다 string. 즉, 다른 옵션 중 두 배의 메모리가 필요합니다. 버퍼를 이동할 방법이 없습니다. 큰 파일의 경우 이는 상당한 페널티이며, 아마도 할당 실패를 야기 할 수도 있습니다.
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'ing 한 결과를보고 싶습니다.)


3
관련 : 다양한 방법의 시간 성능 비교 : C ++에서 한 번에 전체 파일 읽기
jfs

12
오늘까지는 파일 크기가 아닌 결과를보고하는 tellg ()를 본 적이 없습니다. 버그의 근원을 찾기 위해 몇 시간을 보냈습니다. tellg ()를 사용하여 파일 크기를 얻지 마십시오. stackoverflow.com/questions/22984956/…
Puzomor Croatia

당신은 ifs.seekg(0, ios::end)전에 전화해서는 안 tellg됩니까? 파일을 연 직후 포인터를 읽는 포인터가 시작에 tellg0을 반환
Andriy Tylychko

1
당신이 역 참조 것이다으로도 당신은 빈 파일을 검사 할 필요 nullptr&bytes[0]
안드리 Tylychko

좋아, 내가보고 싶었어요 ios::ate내가 마지막에 명시 적으로 움직이는 버전이 더 읽을 수있을 거라고 생각 때문에,
안드리 Tylychko

50

가장 짧은 변형 : Live On Coliru

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

헤더가 필요합니다 <iterator>.

이 방법이 문자열을 미리 할당하고 사용하는 것보다 느리다는보고가있었습니다 std::istream::read. 그러나 최적화가 활성화 된 최신 컴파일러에서는 더 이상 그렇지 않은 것처럼 보이지만 다양한 방법의 상대적인 성능은 컴파일러에 크게 의존하는 것으로 보입니다.


7
이 답변에 대해 설명해 주시겠습니까? 어쨌든 stirng 메모리를 미리 할당하기 위해 한 번에 한 문자 씩 파일을 읽는 것이 얼마나 효율적입니까?
Martin Beckett

@ 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

동의했다. 로 operator>>읽어 들일 때 std::basic_streambuf입력 스트림을 소비하므로 (왼쪽에 남은) 루프가 필요하지 않습니다.
Remy Lebeau

15

C ++ 17 (std :: filesystem)이있는 경우이 방법도 있습니다 ( 및 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표준 라이브러리가 아직 완전히 ++ 17 C를 지원하지 않는 경우. 당신은 교체해야 할 수도 있습니다 result.data()&result[0]는 지원하지 않는 경우 const가 아닌 표준 : : basic_string 데이터를 .


1
정의되지 않은 동작이 발생할 수 있습니다. 텍스트 모드에서 파일을 열면 일부 운영 체제의 디스크 파일과 다른 스트림이 생성됩니다.
MM

1
boost::filesystemc ++ 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();
}

원래 방법에 오류 확인을 추가하는 것이 예상만큼 사소하지 않기 때문에이 답변을 추가하고 있습니다. 원래 메소드는 문자열 스트림의 삽입 연산자 ( str_stream << file_stream.rdbuf())를 사용합니다. 문제는 문자가 삽입되지 않을 때 문자열 스트림의 페일 비트를 설정한다는 것입니다. 오류로 인한 것일 수도 있고 파일이 비어 있기 때문일 수도 있습니다. 페일 비트를 검사하여 실패를 확인하면 빈 파일을 읽을 때 오 탐지가 발생합니다. 파일이 비어 있기 때문에 문자를 삽입하지 않는 합법적 인 실패와 문자를 삽입하는 "실패"를 어떻게 명확하게합니까?

빈 파일을 명시 적으로 검사한다고 생각할 수도 있지만 더 많은 코드와 관련 오류 검사입니다.

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

여기서 장점은 예약을 먼저 수행하므로 내용을 읽을 때 문자열을 늘릴 필요가 없다는 것입니다. 단점은 문자별로 char를 수행한다는 것입니다. 더 스마트 한 버전은 전체 읽기 buf를 잡고 언더 플로를 호출 할 수 있습니다.


1
문자열이 아닌 초기 읽기에 std :: vector를 사용하는이 코드 버전을 체크 아웃해야합니다. 훨씬 더 빠릅니다.
paxos1977

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

filepathA가 아닌 std::string, 그것은이다 std::filesystem::path. 밝혀 std::ifstream::open뿐만 아니라 그 중 하나를받을 수 있습니다.
David G

@DavidG, std::filesystem::path암시 적으로 변환 가능std::string
Jeffrey Cash

cppreference.com에 따르면 수락 하는 ::open멤버 함수 는 메소드가 경로에서 호출 된 것처럼 작동 합니다. 경로 의 기본 은 POSIX에 있습니다. std::ifstreamstd::filesystem::path::c_str()::value_typechar
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 배 정도 느립니다.
David

파일에 "eof"(예 : 0x00, 0xff, ...) 문자가없는 경우에만 작동합니다. 있는 경우 파일의 일부만 읽습니다.
올라프 Dietsche

2

std :: string의 const char * 버퍼에 쓰지 마십시오. 절대로! 그렇게하는 것은 큰 실수입니다.

std :: string에서 전체 문자열을위한 공간을 확보하고, 적당한 크기의 파일에서 청크를 버퍼로 읽은 다음 append ()하십시오. 청크 크기는 입력 파일 크기에 따라 다릅니다. 다른 모든 휴대용 및 STL 호환 메커니즘은 동일하게 작동합니다 (아직 더 예쁘게 보일 수 있음).


5
C ++ 11부터는 std::string버퍼에 직접 쓰는 것이 괜찮습니다 . 그리고 이전의 모든 실제 구현에서 제대로 작동했다고 생각합니다.
MM

1
C ++ 17부터는 std::string::data()같은 트릭에 의존하지 않고 문자열 버퍼를 직접 수정하는 비 const 메소드가 &str[0]있습니다.
zett42

@ zett42와 동의 한이 답변은 사실 틀
렸습니다

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

두 가지 중요한 차이점이 있습니다.

tellg()파일 시작 이후 오프셋을 바이트 단위로 반환한다고 보장 할 수 없습니다. 그 대신 Puzomor Croatia가 지적했듯이 fstream 호출에서 사용할 수있는 토큰에 가깝습니다. gcount()그러나 않는 포맷되지 않은 바이트의 양이 지난 추출을 반환합니다. 따라서 우리는 파일을 열고 모든 내용을 추출하고 버립니다.ignore() 하여 파일 크기를 구한 다음이를 기반으로 출력 문자열을 구성합니다.

둘째, 파일 데이터 std::vector<char>를 a 에서 a 로 복사하지 않아도 됩니다.std::string 문자열에 직접 써서 됩니다.

성능면에서, 적절한 크기의 문자열을 미리 할당하고 read()한 번 호출 하면 절대적으로 가장 빠릅니다 . 흥미로운 사실로서, 사용 ignore()하고 countg()대신 atetellg()GCC에 대한 것은 아래로 컴파일 거의 같은 것을 조금씩, 비트.


1
이 코드는 작동하지 않습니다. 빈 문자열이 나타납니다. 나는 당신이 ifs.seekg(0)대신 원한다고 생각합니다 ifs.clear()(그런 다음 작동합니다).
Xeverous

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.