2008-09-22 10 views
117

¿Cómo leo un archivo en un std::string, es decir, leo todo el archivo a la vez?¿Cuál es la mejor manera de leer un archivo completo en std :: string en C++?

El llamador debe especificar el modo de texto o binario. La solución debe ser estándar, portátil y eficiente. No debería copiar innecesariamente los datos de la cadena, y debería evitar reasignaciones de memoria mientras lee la cadena.

Una forma de hacer esto sería obtener información del tamaño del archivo, cambiar el tamaño de la std::string y fread() en el std::string 's const_cast<char*>()' ed data(). Esto requiere que los datos de std::string sean contiguos, lo que no es requerido por el estándar, pero parece ser el caso para todas las implementaciones conocidas. Lo que es peor, si el archivo se lee en modo texto, el tamaño std::string puede no ser igual al tamaño del archivo.

A soluciones totalmente correctas, estándar conformes y portátiles podrían ser construidos usando std::ifstream 's rdbuf() en un std::ostringstream y desde allí en un std::string. Sin embargo, esto podría copiar los datos de cadena y/o reasignar innecesariamente la memoria. ¿Son todas las implementaciones de bibliotecas estándar relevantes lo suficientemente inteligentes como para evitar todos los gastos generales innecesarios? ¿Hay alguna otra forma de hacerlo? ¿Perdí alguna función oculta de Boost que ya proporciona la funcionalidad deseada?

Muestre su sugerencia de cómo implementarlo.

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

teniendo en cuenta la discusión anterior.

+1

¿Alguien más piensa que esto se parece mucho a una tarea de clase? – Owen

+42

No realmente. Me parece una tarea muy común. – Dima

+1

Aparentemente, esta pregunta es tan relevante como siempre: dos años después, las dos soluciones más eficientes * todavía * copian todo el contenido del archivo en la memoria, y esta copia no puede ser eliminada por el optimizador. Esta es una situación bastante insatisfactoria. –

Respuesta

10

Uso

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

o algo muy cercano. No tengo una referencia stdlib abierta para verificarme a mí mismo.

Sí, entiendo que no escribí la función slurp como se me pidió.

+0

Tiene un error de sintaxis allí, creo que omitió un << before std :: endl. –

+0

El bit de fin de archivo no está establecido y sstr.cout() no existe. Debe ser sstr.str() – nutario

+0

+1 para usar cadenas de transmisión. Los comentarios anteriores son correcciones de sintaxis válidas. –

5

Nunca escriba en el buffer const char * de std :: string. ¡Nunca jamás! Hacerlo es un gran error.

Reserve() espacio para toda la cadena en su std :: cadena, lea los fragmentos de su archivo de tamaño razonable en un búfer y añádalos(). El tamaño de los trozos depende del tamaño del archivo de entrada. Estoy bastante seguro de que todos los demás mecanismos portátiles y compatibles con STL harán lo mismo (aunque pueden parecer más bonitos).

26

La variante más corta: Live On Coliru

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

Se requiere la cabecera <iterator>.

Hubo algunos informes de que este método es más lento que la asignación previa de la cadena y el uso de std::istream::read. Sin embargo, en un compilador moderno con optimizaciones habilitadas, este ya no parece ser el caso, aunque el rendimiento relativo de varios métodos parece ser altamente dependiente del compilador.

+6

¿Podría explicar esta respuesta? ¿Cuán eficiente es, lee un archivo de a una por vez, de todos modos para preasignar la memoria de agitación? –

+0

@ M.M La forma en que leo esta comparación, este método es más lento que el método puro de lectura en C++ en un buffer preasignado. –

+0

Tiene razón, es un caso del título bajo el código de muestra, en lugar de encima :) –

3

Algo como esto no debería ser demasiado malo:.

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

La ventaja aquí es que podemos hacer la reserva en primer lugar por lo que no tendremos que hacer crecer la cadena como leemos cosas en la desventaja es que lo hagamos char por char Una versión más inteligente podría capturar toda la lectura buf y luego llamar a subdesbordamiento.

+1

Debe verificar la versión de este código que usa std :: vector para la lectura inicial en lugar de una cadena. Mucho más rápido. – paxos1977

2

Puede usar la función 'std :: getline' y especificar 'eof' como delimitador. El código resultante es un poco oscura sin embargo:

std::string data; 
std::ifstream in("test.txt"); 
std::getline(in, data, std::string::traits_type::to_char_type( 
        std::string::traits_type::eof())); 
+4

Acabo de probar esto, parece ser mucho más lento que obtener el tamaño del archivo y la lectura de llamadas para todo el tamaño del archivo en un búfer. En el orden de 12 veces más lento. – David

+0

Esto solo funcionará, siempre que no haya caracteres "eof" (por ejemplo, 0x00, 0xff, ...) en su archivo. Si los hay, solo leerá parte del archivo. –

95

Y el más rápido (que yo sepa, descontando los archivos mapeados en memoria):

string str(static_cast<stringstream const&>(stringstream() << in.rdbuf()).str()); 

Esto requiere la cabecera adicional <sstream> para el flujo de cadena . (El static_cast es necesario ya que operator << devuelve un viejo y simple ostream& pero sabemos que en realidad es un stringstream& por lo que el reparto es seguro.)

dividido en varias líneas, moviendo el temporal en una variable, se obtiene una más legible código:

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

O, una vez más en una sola línea:

string slurp(ifstream& in) { 
    return static_cast<stringstream const&>(stringstream() << in.rdbuf()).str(); 
} 
+6

¿De qué sirve convertirlo en un oneliner? Yo siempre optaría por un código legible. Como entusiasta de VB.Net autoproclamado (IIRC), ¿creo que deberías entender el sentimiento? – sehe

+5

@sehe: esperaría que cualquier codificador de C++ medio competente comprenda rápidamente ese trazador. Es bastante manso en comparación con otras cosas que están alrededor. – DevSolar

+18

@DevSolar Bueno, la versión más legible es ~ 30% más corta, carece de un yeso y es equivalente. Por lo tanto, mi pregunta es: "¿De qué sirve convertirlo en un oneliner?" – sehe

38

Ver this answer sobre una cuestión similar.

Para su comodidad, estoy volver a colocar la solución de 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); 
} 

Esta solución resultó en tiempos de ejecución de alrededor del 20% más rápido que las otras respuestas presentadas aquí, al tomar el promedio de 100 carreras en contra del texto de Moby Dick (1.3M). No está mal para una solución portátil de C++, me gustaría ver los resultados de mmap'ing el archivo;)

+3

relacionado: comparación del rendimiento de tiempo de varios métodos: [Lectura en un archivo completo a la vez en C++] (http://insanecoding.blogspot.ru/2011/11/reading-in-entire-file-at-once-in- c.html) – jfs

+2

Hasta el día de hoy, nunca he visto a tellg() informando resultados que no sean de tamaño de archivo. Me tomó horas encontrar la fuente del error. No use tellg() para obtener el tamaño del archivo. http://stackoverflow.com/questions/22984956/tellg-function-give-wrong-size-of-file/22986486#22986486 –

+0

no debería llamar 'ifs.seekg (0, ios :: end)' antes 'tellg'? justo después de abrir un archivo, el puntero de lectura está al principio y, por lo tanto, 'tellg' devuelve cero –

-1

¿Qué pasa si estás sorbiendo un archivo de 11K, entonces tienes que hacerlo en una serie de trozos, por lo que tiene que usar algo como std :: vector para sorber en grandes trozos de cadenas.

1

Si tiene C++ 17 (std :: sistema de archivos), hay también de esta manera (que se hace el tamaño del archivo a través de std::filesystem::file_size en lugar de seekg y 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 }; 

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

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

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

    return result; 
} 

Nota: se puede necesitar usar <experimental/filesystem> y std::experimental::filesystem si su biblioteca estándar aún no es totalmente compatible con C++ 17. Es posible que también necesite reemplazar result.data() con &result[0] si no es compatible con non-const std::basic_string data.

4

No tengo suficiente reputación para comentar directamente las respuestas usando tellg().

Tenga en cuenta que tellg() puede devolver -1 en caso de error. Si está pasando el resultado de tellg() como parámetro de asignación, primero debe verificar el resultado.

Un ejemplo del problema:

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

En el ejemplo anterior, si tellg() encuentra un error se devolverá -1. La conversión implícita entre firmado (es decir, el resultado de tellg()) y sin firmar (es decir, el arg al constructor vector<char>) dará como resultado un vector asignando erróneamente un muy gran cantidad de bytes. (Probablemente. 4294967295 bytes, o 4 GB)

Modificación de la respuesta de paxos1977 para dar cuenta de lo anterior:

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

Aunque no puedo dar fe de la validez del comentario/respuesta, haces un muy buen trabajo al proporcionar código y detalles para tu publicaciones. ¡Sigan así! –

2

Esta solución añade comprobación de errores para la rdbuf() - Método basado.

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

Agregué esta respuesta porque agregar la verificación de errores al método original no es tan trivial como cabría esperar. El método original utiliza el operador de inserción de cadena de caracteres (str_stream << file_stream.rdbuf()). El problema es que esto establece el bit de fallas de la cadena de caracteres cuando no se insertan caracteres. Eso puede deberse a un error o puede deberse a que el archivo está vacío. Si verifica las fallas inspeccionando el failbit, encontrará un falso positivo cuando lea un archivo vacío. ¿Cómo se desajusta la falla legítima para insertar caracteres y "falla" para insertar caracteres porque el archivo está vacío?

Puede pensar en buscar explícitamente un archivo vacío, pero eso es más código y comprobación de errores asociados.

La comprobación de la condición de falla str_stream.fail() && !str_stream.eof() no funciona, porque la operación de inserción no establece el eofbit (en el ostringstream ni en el ifstream).

Entonces, la solución es cambiar la operación. En lugar de usar el operador de inserción de ostringstream (< <), use el operador de extracción de ifstream (>>), que establece el eofbit. Luego verifique la condición de falla file_stream.fail() && !file_stream.eof().

Es importante saber que cuando file_stream >> str_stream.rdbuf() encuentra una falla legítima, nunca debería establecer eofbit (de acuerdo con mi comprensión de la especificación). Eso significa que la verificación anterior es suficiente para detectar fallas legítimas.

Cuestiones relacionadas