Como leio um arquivo inteiro em uma std :: string em C ++?


178

Como faço para ler um arquivo em um std::string, ou seja, ler o arquivo inteiro de uma só vez?

O modo texto ou binário deve ser especificado pelo chamador. A solução deve ser compatível com os padrões, portátil e eficiente. Ele não deve copiar desnecessariamente os dados da sequência e evitar realocações de memória durante a leitura da sequência.

Uma maneira de fazer isso seria declarar o tamanho do arquivo, redimensionar o std::stringe fread()para o std::string's const_cast<char*>()' ed data(). Isso requer que os std::stringdados sejam contíguos, o que não é exigido pelo padrão, mas parece ser o caso de todas as implementações conhecidas. O que é pior, se o arquivo for lido no modo de texto, o std::stringtamanho do arquivo pode não ser igual ao tamanho do arquivo.

Soluções totalmente corretas, compatíveis com os padrões e portáteis podem ser construídas usando std::ifstream's rdbuf()em ae std::ostringstreamde lá em a std::string. No entanto, isso pode copiar os dados da string e / ou realocar desnecessariamente a memória.

  • Todas as implementações relevantes de bibliotecas padrão são inteligentes o suficiente para evitar toda a sobrecarga desnecessária?
  • tem outro jeito de fazer isto?
  • Perdi alguma função Boost oculta que já fornece a funcionalidade desejada?


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

Observe que você ainda tem algumas coisas subespecificadas. Por exemplo, qual é a codificação de caracteres do arquivo? Você tentará detectar automaticamente (que funciona apenas em alguns casos específicos)? Você vai honrar, por exemplo, cabeçalhos XML informando a codificação do arquivo? Também não existe "modo texto" ou "modo binário" - você está pensando em FTP?
— Jason Cohen

O modo texto e binário são hacks específicos para MSDOS e Windows que tentam contornar o fato de que as novas linhas são representadas por dois caracteres no Windows (CR / LF). No modo de texto, eles são tratados como um caractere ('\ n').
— Ferruccio

1
Embora não seja exatamente uma duplicata, isso está intimamente relacionado a: como pré-alocar memória para um objeto std :: string? (que, ao contrário da declaração de Konrad acima, incluía código para fazer isso, lendo o arquivo diretamente no destino, sem fazer uma cópia extra).
— Jerry Coffin

1
"contíguo não é exigido pelo padrão" - sim, é uma maneira indireta. Assim que você usa op [] na string, ela deve ser combinada em um buffer gravável contíguo, para garantir a segurança de gravar em & str [0] se você redimensionar () o suficiente primeiro. E no C ++ 11, a string é simplesmente sempre contígua.
— Tino Didriksen

2
Link relacionado: Como ler um arquivo em C ++? - benchmarks e discute as várias abordagens. E sim, rdbuf(aquele na resposta aceita) não é o mais rápido, readé.
— legends2k

Respostas:


138

Uma maneira é liberar o buffer de fluxo em um fluxo de memória separado e depois convertê-lo para std::string:

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

Isso é bem conciso. No entanto, conforme observado na pergunta, isso executa uma cópia redundante e, infelizmente, não existe maneira fundamental de excluir essa cópia.

A única solução real que evita cópias redundantes é fazer a leitura manualmente em um loop, infelizmente. Como o C ++ agora tem seqüências de caracteres contíguas garantidas, pode-se escrever o seguinte (≥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
Qual o sentido de torná-lo um oneliner? Eu sempre optaria por código legível. Como um entusiasta autônomo do VB.Net (IIRC), acho que você deve entender o sentimento?
— sehe

5
@sehe: Eu esperaria que qualquer codificador C ++ de competência intermediária entendesse prontamente esse código. É bem manso comparado a outras coisas por aí.
— DevSolar

43
@DevSolar Bem, a versão mais legível é ~ 30% mais curta, carece de elenco e é equivalente. Minha pergunta, portanto, permanece: "Qual é o sentido de torná-lo oneliner?"
— Veja

13
note: esse método lê o arquivo no buffer do stringstream e copia todo o buffer no string. Ou seja, exigindo o dobro de memória que algumas das outras opções. (Não há como mover o buffer). Para um arquivo grande, isso seria uma penalidade significativa, talvez até causando uma falha na alocação.
— 6166 MM

9
@ DanNissenbaum Você está confundindo algo. A concisão é realmente importante na programação, mas a maneira correta de alcançá-la é decompor o problema em partes e encapsular-o em unidades independentes (funções, classes, etc.). A adição de funções não diminui a concisão; pelo contrário.
— 21416 Konrad Rudolph

52

Veja esta resposta em uma pergunta semelhante.

Para sua comodidade, estou reposicionando a solução dos 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);
}

Essa solução resultou em tempos de execução cerca de 20% mais rápidos do que as outras respostas apresentadas aqui, ao fazer a média de 100 execuções contra o texto de Moby Dick (1,3M). Nada mal para uma solução portátil C ++, eu gostaria de ver os resultados de mmap'ing do arquivo;)


3
relacionada: tempo comparação de desempenho de vários métodos: Leitura em um arquivo inteiro de uma vez em C ++
— jfs

12
Até hoje, nunca havia testemunhado o tellg () relatando resultados sem tamanho de arquivo. Levei horas para encontrar a fonte do bug. Por favor, não use tellg () para obter o tamanho do arquivo. stackoverflow.com/questions/22984956/…
— Puzomor Croatia

você não deveria ligar ifs.seekg(0, ios::end)antes tellg? apenas depois de abrir um arquivo de leitura ponteiro está no início e assim tellgretorna zero
— Andriy Tylychko

1
Também é preciso verificar para arquivos vazios como você vai desreferenciava nullptrpor&bytes[0]
— Andriy Tylychko

ok, eu perdi ios::ate, então eu acho que uma versão com movimento explícita à final seria mais legível
— Andriy Tylychko

50

A variante mais curta: Live On Coliru

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

Requer o cabeçalho <iterator>.

Houve alguns relatos de que esse método é mais lento do que pré-alocar a string e usá-la std::istream::read. No entanto, em um compilador moderno com otimizações ativadas, isso não parece mais ser o caso, embora o desempenho relativo de vários métodos pareça ser altamente dependente do compilador.


7
Você poderia explicar essa resposta. Quão eficaz é: ele lê um arquivo de cada vez, para pré-alocar a memória de agitação?
— Martin Beckett

@MM A maneira como li essa comparação, esse método é mais lento que o método C ++ puro de leitura em um buffer pré-alocado.
— 27516 Konrad Rudolph #:

Você está certo, é um caso do ser título sob o exemplo de código, em vez de acima dela :)
— MM

@juzzlin C ++ não funciona assim. Não exigir um cabeçalho em um ambiente específico não é um bom motivo para você não incluí-lo.
— LF

Esse método acionará a realocação de memória várias vezes?
— coin cheung

22

Usar

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

ou algo muito próximo. Não tenho uma referência stdlib aberta para me verificar novamente.

Sim, entendo que não escrevi a slurpfunção conforme solicitado.


Parece bom, mas não compila. As alterações para compilá-lo reduzem-no a outras respostas nesta página. ideone.com/EyhfWm
— JDiMatteo

5
Por que o loop while?
— Zitrax 19/10

Acordado. Quando operator>>lê em a std::basic_streambuf, ele consome (o que resta) do fluxo de entrada, portanto o loop é desnecessário.
— Remy Lebeau

15

Se você possui C ++ 17 (std :: filesystem), também existe este caminho (que obtém o tamanho do arquivo em std::filesystem::file_sizevez de seekge 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;
}

Nota : pode ser necessário usá-lo <experimental/filesystem>e std::experimental::filesystemse sua biblioteca padrão ainda não suportar totalmente o C ++ 17. Você também pode precisar substituí result.data()- &result[0]lo por se ele não suportar dados não-const std :: basic_string .


1
Isso pode causar comportamento indefinido; abrir o arquivo no modo de texto gera um fluxo diferente do arquivo de disco em alguns sistemas operacionais.
— MM

1
Originalmente desenvolvido boost::filesystempara que você também possa usar o boost se não tiver c ++ 17
— Gerhard Burger

2
Abrir um arquivo com uma API e obter seu tamanho com outra parece exigir inconsistência e condições de corrida.
— Arthur Tacca 24/10

14

Eu não tenho reputação suficiente para comentar diretamente sobre as respostas usando tellg().

Esteja ciente de que tellg()pode retornar -1 em caso de erro. Se você estiver passando o resultado tellg()como um parâmetro de alocação, verifique primeiro o sanidade.

Um exemplo do problema:

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

No exemplo acima, se tellg()encontrar um erro, ele retornará -1. A conversão implícita entre assinado (ou seja, o resultado de tellg()) e não assinado (ou seja, o argumento para o vector<char>construtor) resultará em um vetor que aloca erroneamente um número muito grande de bytes. (Provavelmente 4294967295 bytes ou 4 GB.)

Modificando a resposta de paxos1977 para explicar o que foi dito acima:

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

Esta solução adiciona a verificação de erros ao método baseado em 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();
}

Estou adicionando esta resposta porque adicionar verificação de erros ao método original não é tão trivial quanto você esperaria. O método original usa o operador de inserção do stringstream ( str_stream << file_stream.rdbuf()). O problema é que isso define o failbit do stringstream quando nenhum caractere é inserido. Isso pode ocorrer devido a um erro ou ao arquivo estar vazio. Se você verificar falhas ao inspecionar o failbit, encontrará um falso positivo ao ler um arquivo vazio. Como você desambigua a falha legítima em inserir caracteres e a "falha" em inserir caracteres porque o arquivo está vazio?

Você pode procurar explicitamente um arquivo vazio, mas isso significa mais código e verificação de erro associada.

A verificação da condição de falha str_stream.fail() && !str_stream.eof()não funciona, porque a operação de inserção não define o eofbit (no ostringstream nem no ifstream).

Então, a solução é mudar a operação. Em vez de usar o operador de inserção do ostringstream (<<), use o operador de extração do ifstream (>>), que define o eofbit. Em seguida, verifique a condição de falha file_stream.fail() && !file_stream.eof().

É importante file_stream >> str_stream.rdbuf()ressaltar que , quando encontra uma falha legítima, ele nunca deve definir o eofbit (de acordo com meu entendimento da especificação). Isso significa que a verificação acima é suficiente para detectar falhas legítimas.


3

Algo assim não deve ser tão ruim:

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

A vantagem aqui é que fazemos a reserva primeiro, para que não tenhamos que crescer a string enquanto lemos as coisas. A desvantagem é que fazemos char a char. Uma versão mais inteligente pode pegar todo o buf de leitura e, em seguida, chamar underflow.


1
Você deve fazer o checkout da versão desse código que usa std :: vector para a leitura inicial, em vez de uma string. Muito, muito mais rápido.
— 22420 paxos1977

3

Aqui está uma versão usando a nova biblioteca de sistemas de arquivos com verificação de erro razoavelmente robusta:

#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.opentambém pode aceitar std::stringsem converter com.c_str()
— Matt Eding

filepathnão é um std::string, é um std::filesystem::path. Acontece que std::ifstream::openpode aceitar um desses também.
— David G

@DavidG, std::filesystem::pathé implicitamente conversível emstd::string
— Jeffrey Cash

De acordo com cppreference.com, a ::openfunção de membro std::ifstreamque aceita std::filesystem::pathopera como se o ::c_str()método fosse chamado no caminho. O subjacente ::value_typedos caminhos está charno POSIX.
— David G

2

Você pode usar a função 'std :: getline' e especificar 'eof' como delimitador. O código resultante é um pouco obscuro:

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
Acabei de testar isso, parece ser muito mais lento do que obter o tamanho do arquivo e chamar a leitura para todo o tamanho do arquivo em um buffer. Da ordem de 12x mais lento.
— David

Isso só funcionará, desde que não haja caracteres "eof" (por exemplo, 0x00, 0xff, ...) em seu arquivo. Se houver, você lerá apenas parte do arquivo.
— Olaf Dietsche

2

Nunca escreva no buffer const char * do std :: string. Jamais! Fazer isso é um erro enorme.

Reserve () espaço para toda a cadeia de caracteres em seu std :: string, leia trechos do seu arquivo de tamanho razoável em um buffer e inclua (). O tamanho dos pedaços depende do tamanho do arquivo de entrada. Tenho certeza de que todos os outros mecanismos portáteis e compatíveis com STL farão o mesmo (mas podem parecer mais bonitos).


5
Desde o C ++ 11, é garantido que seja bom gravar diretamente no std::stringbuffer; e acredito que funcionou corretamente em todas as implementações reais anteriores a isso #
— MM

1
Desde o C ++ 17, temos até um std::string::data()método não-const para modificar o buffer de strings diretamente, sem recorrer a truques como &str[0].
— zett42

Concordado com @ zett42, esta resposta está incorreta
— jeremyong 15/03

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

uso:

const string logAsString = GetFileAsString(logFilePath);

0

Uma função atualizada que se baseia na solução dos 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;
}

Existem duas diferenças importantes:

tellg()Não é garantido o retorno do deslocamento em bytes desde o início do arquivo. Em vez disso, como Puzomor Croatia apontou, é mais um token que pode ser usado nas chamadas fstream. gcount()no entanto , retorna a quantidade de bytes não formatados extraídos pela última vez. Portanto, abrimos o arquivo, extraímos e descartamos todo o seu conteúdo ignore()para obter o tamanho do arquivo e construímos a string de saída com base nisso.

Em segundo lugar, evitamos ter que copiar os dados do arquivo de a std::vector<char>para a std::stringescrevendo diretamente na string.

Em termos de desempenho, esse deve ser o mais rápido possível, alocando a sequência de tamanho apropriada com antecedência e ligando read()uma vez. Como um fato interessante, usar ignore()e em countg()vez de atee tellg()no gcc compila quase a mesma coisa , pouco a pouco.


1
Este código não funciona, estou recebendo uma string vazia. Eu acho que você queria, em ifs.seekg(0)vez de ifs.clear()(então funciona).
— Xeverous

-1
#include <iostream>
#include <fstream>
#include <string.h>
using namespace std;
main(){
    fstream file;
    file.open("test.txt");
    string copy,temp;
    while(getline(file,temp)){
        copy+=temp;
        copy+="\n";
    }
    cout<<copy;
    file.close();
}

1
Por favor, adicione a descrição.
— Peter

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.