programing

C++의 std:: 문자열로 전체 파일을 읽는 방법은 무엇입니까?

iphone6s 2023. 8. 5. 09:57
반응형

C++의 std:: 문자열로 전체 파일을 읽는 방법은 무엇입니까?

을 파을어읽까니로 ?std::string즉, 한 번에 전체 파일을 읽으시겠습니까?

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

한 크기를 를 조정하는 입니다.std::string그리고.fread()std::stringconst_cast<char*>()에드data()이를 위해서는 다음이 필요합니다.std::string의 데이터는 표준에서 요구하지 않지만 알려진 모든 구현의 경우에 해당합니다.더 나쁜 것은, 파일이 텍스트 모드에서 읽히면,std::string의 크기가 파일의 크기와 같지 않을 수 있습니다.

다음을 사용하여 완전히 정확하고 표준을 준수하는 휴대용 솔루션을 구성할 수 있습니다.std::ifstreamrdbuf()의 상태가.std::ostringstream그리고 거기서 부터 a로.std::string그러나 이 경우 문자열 데이터를 복사하거나 불필요하게 메모리를 재할당할 수 있습니다.

  • 모든 관련 표준 라이브러리 구현이 불필요한 오버헤드를 방지할 수 있을 정도로 현명합니까?
  • 다른 방법이 있습니까?
  • 내가 이미 원하는 기능을 제공하는 숨겨진 부스트 기능을 놓쳤습니까?


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

, 을 " " " " " " " " " " " " " " " " " " " " " 으로 입니다.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);

    if (not stream) {
        throw std::ios_base::failure("file does not exist");
    }
    
    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;
}

가장 짧은 변형:

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

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

이 하고 사용하는 것보다 .std::istream::read그러나 최적화가 가능한 최신 컴파일러에서는 다양한 방법의 상대적 성능이 컴파일러에 크게 의존하는 것처럼 보이지만 더 이상 그렇지 않은 것 같습니다.

유사한 질문에 대한답변을 참조하십시오.

편의를 위해 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(130M) 텍스트에 대해 평균 100회 실행했을 때 여기에 제시된 다른 답변보다 실행 시간이 약 20% 단축되었습니다.휴대용 C++ 솔루션치고는 나쁘지 않습니다, 파일을 mmapping 한 결과를 보고 싶습니다 ;)

system)을 사용하는 ++17(std:: file ).std::filesystem::file_sizeseekg그리고.tellg):

#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을 완전히 지원하지 않는 경우. 또교해야할있다니습수도체한다니있▁also습▁you▁need▁to▁replace▁might수도할을 대체해야 할 수도 있습니다.result.data()와 함께&result[0]nonconstd::basic_string 데이터를 지원하지 않는 경우.

사용하다

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

아니면 아주 가까운 것.내 자신을 다시 확인할 수 있는 stddlib 참조가 없습니다.

네, 제가 작성하지 않은 것은 이해합니다.slurp요구대로 기능합니다.

다음을 사용하여 응답에 대해 직접 언급할 만큼 평판이 좋지 않습니다.tellg().

▁please니▁that▁aware▁be.tellg()오류 시 -1을 반환할 수 있습니다.만약 당신이 그 결과를 통과한다면.tellg()할당 매개 변수로 먼저 결과를 제대로 확인해야 합니다.

문제의 예:

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

예에서, 위의예에서라면, 약만.tellg()오류가 발생하면 -1을 반환합니다.서명된 사이의 암묵적 주조(즉, 결과)tellg()즉, 에서 arg로)입니다.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);
}

이것이 널리 사용되는 유틸리티처럼 보이기 때문에 제 접근 방식은 특히 부스트 라이브러리가 프로젝트에서 이미 링크되어 있는 경우(linker flags -lboost_system -lboost_filesystem) 수동 솔루션보다 이미 사용 가능한 라이브러리를 검색하고 선호하는 것입니다.여기서 boost는 load_string_file 유틸리티를 제공합니다.

#include <iostream>
#include <string>
#include <boost/filesystem/string_file.hpp>

int main() {
    std::string result;
    boost::filesystem::load_string_file("aFileName.xyz", result);
    std::cout << result.size() << std::endl;
}

장점으로 이 함수는 크기를 결정하기 위해 전체 파일을 검색하지 않고 내부적으로 stat()을 사용합니다.할 수 있는 코드를 할 때 할 수 : 하게 그나무수있단점으로 조정됩니다. 문자열은 불필요하게 크기가 조정됩니다.'\0'파일 내용으로 다시 작성되는 문자입니다.

이 솔루션은 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()삽입 작업이 (ostringstream 또는 ifstream에서) eofit를 설정하지 않기 때문에 작동하지 않습니다.

그래서, 그 해결책은 운영을 바꾸는 것입니다.를 사용하는 ostringstream을 를 합니다. 이 연산자는 ifstream의 삽입 연산자(>)입니다. 다음 인 "고장"을 확인합니다.file_stream.fail() && !file_stream.eof().

중요한 것은, 언제file_stream >> str_stream.rdbuf()합법적인 오류가 발생할 경우, (사양에 대한 제 이해에 따라) 비트를 설정해서는 안 됩니다.즉, 위의 검사는 정당한 오류를 감지하기에 충분합니다.

이와 같은 것은 그리 나쁘지 않을 겁니다.

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

여기서 장점은 우리가 먼저 예약을 해서 우리가 물건을 읽을 때 줄을 늘릴 필요가 없다는 것입니다.단점은 차로 한다는 것입니다.더 똑똑한 버전은 전체 읽기 버프를 잡은 다음 언더플로우를 호출할 수 있습니다.

다음은 상당히 강력한 오류 검사 기능을 갖춘 새 파일 시스템 라이브러리를 사용하는 버전입니다.

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

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

저는 이것이 매우 오래된 질문이라는 것을 압니다. 하지만 그 중에서 제가 이를 위한 가장 확실한 방법을 고려했을 것이라는 것은 언급하지 않았습니다.네, 저는 이것이 C++이라는 것을 알고 있고, libc를 사용하는 것은 사악하고 틀리거나 무엇이든 하지만, 그것은 미친 짓입니다.libc를 사용하는 것은 괜찮습니다, 특히 이와 같은 간단한 것은 그렇습니다.

기본적으로 파일을 열고 크기(반드시 그 순서대로는 아님)를 지정한 후 읽기만 하면 됩니다.

#include <string>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <sys/stat.h>

static constexpr char filename[] = "foo.bar";

int main(void)
{
    FILE *fp = ::fopen(filename, "rb");
    if (!fp) {
        ::perror("fopen");
        ::exit(1);
    }

    // Stat isn't strictly part of the standard C library, 
    // but it's in every libc I've ever seen for a hosted system.
    struct stat st;
    if (::fstat(::fileno(fp), &st) == (-1)) {
        ::perror("fstat");
        ::exit(1);
    }

    // You could simply allocate a buffer here and use std::string_view, or
    // even allocate a buffer and copy it to a std::string. Creating a
    // std::string and setting its size is simplest, but will pointlessly
    // initialize the buffer to 0. You can't win sometimes.
    std::string str;
    str.reserve(st.st_size + 1U);
    str.resize(st.st_size);
    ::fread(str.data(), 1, st.st_size, fp);
    str[st.st_size] = '\0';
    ::fclose(fp);
}

이것은 (실제로) 완전히 휴대할 수 있을 뿐만 아니라 다른 솔루션보다 더 나빠 보이지는 않습니다.물론 즉시 종료하는 대신 예외를 던질 수도 있습니다.크기를 조정하는 것은 나를 심각하게 짜증나게 합니다.std::string항상 0이 초기화되지만 어쩔 수 없습니다.

작업은 C++17 이상에 대해 작성된 대로만 수행됩니다.이전 버전(편집을 허용하지 않아야 함)std::string::data()이전 버전으로 작업하는 경우 교체를 고려하십시오.str.data()와 함께&str[0].

여러 위치에서 정보를 가져오는 중...이것이 가장 빠르고 최선의 방법입니다.

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

//Returns true if successful.
bool readInFile(std::string pathString)
{
  //Make sure the file exists and is an actual file.
  if (!std::filesystem::is_regular_file(pathString))
  {
    return false;
  }
  //Convert relative path to absolute path.
  pathString = std::filesystem::weakly_canonical(pathString);
  //Open the file for reading (binary is fastest).
  std::wifstream in(pathString, std::ios::binary);
  //Make sure the file opened.
  if (!in)
  {
    return false;
  }
  //Wide string to store the file's contents.
  std::wstring fileContents;
  //Jump to the end of the file to determine the file size.
  in.seekg(0, std::ios::end);
  //Resize the wide string to be able to fit the entire file (Note: Do not use reserve()!).
  fileContents.resize(in.tellg());
  //Go back to the beginning of the file to start reading.
  in.seekg(0, std::ios::beg);
  //Read the entire file's contents into the wide string.
  in.read(fileContents.data(), fileContents.size());
  //Close the file.
  in.close();
  //Do whatever you want with the file contents.
  std::wcout << fileContents << L" " << fileContents.size();
  return true;
}

글자로 ▁a▁in로 읽힙니다.std::wstring하지만 당신이 단지 일반적인 캐릭터와 a를 원한다면 당신은 쉽게 적응할 수 있습니다.std::string.

#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);

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 크로아티아가 지적했듯이 fstream 호출 내에서 사용할 수 있는 토큰에 더 가깝습니다.gcount()그러나 마지막으로 추출된 포맷되지 않은 바이트의 양을 반환합니다.따라서 파일을 열고, 파일의 모든 내용을 추출하고 폐기합니다.ignore()파일의 크기를 가져오고 이를 기반으로 출력 문자열을 구성합니다.

.std::vector<char>std::string직접 문자열에 기록함으로써.

성능 측면에서, 이것은 적절한 크기의 문자열을 미리 할당하고 호출하는 가장 빠른 속도여야 합니다.read() 번.로, 한번운사서실로것흥는, 하용사로미것▁once.ignore()그리고.countg()ate그리고.tellg()on gcc는 조금씩 거의 같은 것으로 압축됩니다.

이것은 제가 사용하는 기능이며, 어떤 이유로 대용량 파일(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;
}

성능 면에서 아래 코드보다 빠른 것을 찾을 수 없습니다.

std::string readAllText(std::string const &path)
{
    assert(path.c_str() != NULL);
    FILE *stream = fopen(path.c_str(), "r");
    assert(stream != NULL);
    fseek(stream, 0, SEEK_END);
    long stream_size = ftell(stream);
    fseek(stream, 0, SEEK_SET);
    void *buffer = malloc(stream_size);
    fread(buffer, stream_size, 1, stream);
    assert(ferror(stream) == 0);
    fclose(stream);
    std::string text((const char *)buffer, stream_size);
    assert(buffer != NULL);
    free((void *)buffer);
    return text;
}

제가 개발한 rst 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;
#include <string>
#include <fstream>

int main()
{
    std::string fileLocation = "C:\\Users\\User\\Desktop\\file.txt";
    std::ifstream file(fileLocation, std::ios::in | std::ios::binary);

    std::string data;

    if(file.is_open())
    {
        std::getline(file, data, '\0');

        file.close();
    }
}

작은 크기에서 중간 크기의 파일의 경우 상당히 빠른 이러한 방법을 사용합니다.반환 문자열은 바이트 배열을 문자열로 "변환"하는 데 사용할 수 있습니다.

auto read_file_bytes(std::string_view filepath) -> std::vector<std::byte> {
    std::ifstream ifs(filepath.data(), std::ios::binary | std::ios::ate);

    if (!ifs)
        throw std::ios_base::failure("File does not exist");

    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::ios_base::failure("Read error");

    return buffer;
}

auto read_file_string(std::string_view filepath) -> std::string {
    auto bytes = read_file_bytes(filepath);
    return std::string(reinterpret_cast<char *>(bytes.begin().base()), bytes.size());
}

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

std:: string의 전체 문자열에 대한 공간을 예약하고 적절한 크기의 청크를 파일에서 버퍼로 읽은 다음 추가합니다().청크의 크기는 입력 파일 크기에 따라 달라집니다.다른 모든 휴대용 및 STL 호환 메커니즘도 동일하게 작동할 것이라고 확신합니다(그러나 더 예뻐 보일 수도 있습니다).

#include <iostream>
#include <fstream>
#include <string.h>
using namespace std;
main(){
    fstream file;
    //Open a file
    file.open("test.txt");
    string copy,temp;
    //While loop to store whole document in copy string
    //Temp reads a complete line
    //Loop stops until temp reads the last line of document
    while(getline(file,temp)){
        //add new line text in copy
        copy+=temp;
        //adds a new line
        copy+="\n";
    }
    //Display whole document
    cout<<copy;
    //close the document
    file.close();
}
std::string get(std::string_view const& fn)
{
  struct filebuf: std::filebuf
  {
    using std::filebuf::egptr;
    using std::filebuf::gptr;

    using std::filebuf::gbump;
    using std::filebuf::underflow;
  };

  std::string r;

  if (filebuf fb; fb.open(fn.data(), std::ios::binary | std::ios::in))
  {
    r.reserve(fb.pubseekoff({}, std::ios::end));
    fb.pubseekpos({});

    while (filebuf::traits_type::eof() != fb.underflow())
    {
      auto const gptr(fb.gptr());
      auto const sz(fb.egptr() - gptr);

      fb.gbump(sz);
      r.append(gptr, sz);
    }
  }

  return r;
}

파티에 늦었다는 것은 알고 있지만, 현재(2021년) 제 머신에서 테스트한 것 중 가장 빠른 구현입니다.

#include <fstream>
#include <string>

bool fileRead( std::string &contents, const std::string &path ) {
    contents.clear();
    if( path.empty()) {
        return false;
    }
    std::ifstream stream( path );
    if( !stream ) {
        return false;
    }
    stream >> contents;
    return true;
}

언급URL : https://stackoverflow.com/questions/116038/how-do-i-read-an-entire-file-into-a-stdstring-in-c

반응형