¿Cómo leo un archivo completo en un std::string en C++?

12 minutos de lectura

¿Cómo leo un archivo en un std::stringes decir, leer todo el archivo a la vez?

La persona que llama debe especificar el modo de texto o binario. La solución debe ser compatible con los estándares, portátil y eficiente. No debe copiar innecesariamente los datos de la cadena y debe evitar reasignaciones de memoria mientras lee la cadena.

Una forma de hacer esto sería indicar el tamaño del archivo, cambiar el tamaño del std::string y fread() en el std::string‘s const_cast<char*>()ed data(). Esto requiere la std::stringLos datos de 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 std::stringEl tamaño de ‘s puede no ser igual al tamaño del archivo.

Se podrían construir soluciones completamente correctas, compatibles con los estándares y portátiles utilizando std::ifstream‘s rdbuf() en un std::ostringstream y de ahí a un std::string. Sin embargo, esto podría copiar los datos de la cadena y/o reasignar memoria innecesariamente.

  • ¿Todas las implementaciones de bibliotecas estándar relevantes son lo suficientemente inteligentes como para evitar todos los gastos generales innecesarios?
  • ¿Hay otra forma de hacerlo?
  • ¿Me perdí alguna función Boost oculta que ya proporciona la funcionalidad deseada?

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

  • Tenga en cuenta que todavía tiene algunas cosas subespecificadas. Por ejemplo, ¿cuál es la codificación de caracteres del archivo? ¿Intentará la detección automática (que solo funciona en algunos casos específicos)? ¿Respetará, por ejemplo, los encabezados XML que le indican la codificación del archivo? Además, no existe el “modo de texto” o el “modo binario”. ¿Estás pensando en FTP?

    –Jason Cohen

    22 de septiembre de 2008 a las 16:51

  • El modo de texto y binario son trucos específicos de MSDOS y Windows que intentan evitar el hecho de que las nuevas líneas están representadas por dos caracteres en Windows (CR/LF). En el modo de texto, se tratan como un carácter (‘\n’).

    – Ferruccio

    22 de septiembre de 2008 a las 16:54

  • Aunque no es (bastante) un duplicado exacto, esto está estrechamente relacionado con: ¿cómo preasignar memoria para un objeto std::string? (que, contrariamente a la declaración anterior de Konrad, incluía un código para hacer esto, leyendo el archivo directamente en el destino, sin hacer una copia adicional).

    – Jerry Ataúd

    21 de septiembre de 2012 a las 15:11


  • “Contiguo no es requerido por el estándar” – sí lo es, de forma indirecta. Tan pronto como use op[] en la cadena, debe fusionarse en un búfer grabable contiguo, por lo que se garantiza que es seguro escribir en &str[0] si .resize() lo suficientemente grande primero. Y en C++ 11, la cadena simplemente siempre es contigua.

    –Tino Didriksen

    19 de julio de 2013 a las 17:22

  • Enlace relacionado: ¿Cómo leer un archivo en C++? — puntos de referencia y discute los diversos enfoques. Y si, rdbuf (el de la respuesta aceptada) no es el más rápido, read es.

    – leyendas2k

    27 de noviembre de 2014 a las 4:24


¿Como leo un archivo completo en un stdstring en C
Konrad Rodolfo

Una forma es vaciar el búfer de flujo en un flujo de memoria separado y luego convertirlo en std::string (se omite el manejo de errores):

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

Esto es muy conciso. Sin embargo, como se indica en la pregunta, esto realiza una copia redundante y, lamentablemente, fundamentalmente no hay forma de elidir esta copia.

Desafortunadamente, la única solución real que evita las copias redundantes es hacer la lectura manualmente en bucle. Dado que C++ ahora tiene cadenas contiguas garantizadas, se podría escribir lo siguiente (≥C++17, manejo de errores incluido):

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

  • ¿Cuál es el punto de convertirlo en una sola línea? Siempre optaría por un código legible. Como entusiasta autoproclamado de VB.Net (IIRC), creo que debería entender el sentimiento.

    – seje

    21 de septiembre de 2012 a las 14:32

  • @sehe: esperaría que cualquier codificador de C ++ medianamente competente entienda fácilmente esa línea. Es bastante manso en comparación con otras cosas que existen.

    – DevSolar

    21 de septiembre de 2012 a las 14:36

  • @DevSolar Bueno, la versión más legible es aproximadamente un 30 % más corta, carece de reparto y, por lo demás, es equivalente. Por lo tanto, mi pregunta es: “¿Cuál es el punto de convertirlo en una sola línea?”

    – seje

    21/09/2012 a las 15:00

  • nota: este método lee el archivo en el búfer de stringstream, luego copia todo el búfer en el string. Es decir, requiere el doble de memoria que algunas de las otras opciones. (No hay forma de mover el búfer). Para un archivo grande, esto sería una penalización significativa, tal vez incluso causando una falla en la asignación.

    –MM

    6 de febrero de 2016 a las 12:08


  • @DanNissenbaum Estás confundiendo algo. La concisión es realmente importante en la programación, pero la forma adecuada de lograrla es descomponer el problema en partes y encapsularlas en unidades independientes (funciones, clases, etc.). Agregar funciones no resta valor a la concisión; todo lo contrario.

    – Konrad Rodolfo

    12 de febrero de 2016 a las 8:47

¿Como leo un archivo completo en un stdstring en C
Konrad Rodolfo

La variante más corta: Vive en Coliru

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

Requiere el encabezado <iterator>.

Hubo algunos informes de que este método es más lento que preasignar la cadena y usar 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 depender en gran medida del compilador.

  • ¿Podría ampliar esta respuesta? ¿Qué tan eficiente es? ¿Lee un archivo un carácter a la vez, de todos modos para preasignar la memoria de agitación?

    – Martín Beckett

    22 de septiembre de 2008 a las 17:19

  • @MM La forma en que leí esa comparación, este método es más lento que el método puro de lectura en un búfer preasignado de C ++.

    – Konrad Rodolfo

    6 de febrero de 2016 a las 12:27

  • Tienes razón, es un caso en el que el título está debajo del ejemplo de código, en lugar de encima 🙂

    –MM

    06/02/2016 a las 19:44


  • ¿Este método activará la reasignación de memoria muchas veces?

    – moneda cheung

    11 de marzo de 2020 a las 3:39

  • @coincheung Lamentablemente, sí. Si desea evitar las asignaciones de memoria, debe almacenar manualmente la lectura en búfer. Los flujos de C++ IO son bastante malos.

    – Konrad Rodolfo

    16 de marzo de 2020 a las 10:08

1647570673 471 ¿Como leo un archivo completo en un stdstring en C
paxos1977

Ver esta respuesta en una pregunta similar.

Para su comodidad, vuelvo a publicar 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 un 20 % más rápidos que las otras respuestas presentadas aquí, al tomar el promedio de 100 ejecuciones contra el texto de Moby Dick (1.3M). No está mal para una solución portátil de C++, me gustaría ver los resultados de hacer mmap del archivo;)

  • relacionado: comparación de rendimiento de tiempo de varios métodos: Lectura en un archivo completo a la vez en C++

    – jfs

    3 de diciembre de 2014 a las 20:17

  • Hasta el día de hoy, nunca he visto a tellg() informar 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. stackoverflow.com/questions/22984956/…

    – Puzomor Croacia

    27 de diciembre de 2016 a las 0:36

  • también debe verificar si hay archivos vacíos, ya que eliminará la referencia nullptr por &bytes[0]

    –Andriy Tylychko

    9 de febrero de 2017 a las 15:32

  • @paxos1977> decidir en qué sistemas se define que su programa es correcto depende de usted. Tal como está, se basa en garantías que no proporciona C++ y, como tal, es incorrecto. Si funciona en un conjunto conocido de implementaciones que brindan tales garantías (como en: documentado como garantías, no simplemente “hoy se ve bien en esa versión que tengo”), entonces explíquelo, de lo contrario es engañoso.

    – espectros

    16 de marzo de 2021 a las 1:16


  • Razonamiento perfecto para construir bases de código frágiles que se rompen inesperadamente porque cualquier comportamiento que observé un día era “lo suficientemente portátil”. Hasta que alguien lo cambió. No es como si tuviéramos una historia de una y otra vez. La ingeniería adecuada se realiza basándose en garantías, no probando lo que parece funcionar ahora y esperando lo mejor. Por lo tanto: este código es solo una implementación de ingeniería sólida donde se garantizan sus suposiciones. [note: I did not talk about whether it happens to work or not today, that is irrelevant]

    – espectros

    18 de marzo de 2021 a las 18:04


1647570674 934 ¿Como leo un archivo completo en un stdstring en C
Gabriel Majeri

Si tiene C ++ 17 (std::filesystem), también existe esta forma (que obtiene 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, 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: es posible que necesite usar <experimental/filesystem> y std::experimental::filesystem si su biblioteca estándar aún no es completamente compatible con C++17. Es posible que también deba reemplazar result.data() con &result[0] si no es compatible datos no const std::basic_string.

¿Como leo un archivo completo en un stdstring en C
ben collins

Utilizar

#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 de stdlib abierta para verificarme dos veces.

Sí, entiendo que no escribí el slurp función como se le pide.

  • Esto se ve bien, pero no compila. Los cambios para compilarlo lo reducen a otras respuestas en esta página. ideone.com/EyhfWm

    – JDiMatteo

    29 de junio de 2015 a las 18:13

  • ¿Por qué el ciclo while?

    – Zitrax

    19 oct 2017 a las 12:05

  • Acordado. Cuándo operator>> lee en un std::basic_streambufconsumirá (lo que queda de) el flujo de entrada, por lo que el ciclo no es necesario.

    – Rémy Lebeau

    30 de diciembre de 2018 a las 18:44


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ás pasando el resultado de tellg() como parámetro de asignación, primero debe verificar la cordura del resultado.

Un ejemplo del problema:

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

En el ejemplo anterior, si tellg() encuentra un error, devolverá -1. Casting implícito entre firmado (es decir, el resultado de tellg()) y sin firmar (es decir, el argumento del vector<char> constructor) dará como resultado que su vector asigne erróneamente un muy gran cantidad de bytes. (Probablemente 4294967295 bytes o 4 GB).

Modificando la respuesta de paxos1977 para tener en cuenta 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);
}

  • Esto se ve bien, pero no compila. Los cambios para compilarlo lo reducen a otras respuestas en esta página. ideone.com/EyhfWm

    – JDiMatteo

    29 de junio de 2015 a las 18:13

  • ¿Por qué el ciclo while?

    – Zitrax

    19 oct 2017 a las 12:05

  • Acordado. Cuándo operator>> lee en un std::basic_streambufconsumirá (lo que queda de) el flujo de entrada, por lo que el ciclo no es necesario.

    – Rémy Lebeau

    30 de diciembre de 2018 a las 18:44


1647570675 649 ¿Como leo un archivo completo en un stdstring en C
tgnottingham

Esta solución agrega la verificación de errores al método basado en 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();
}

Estoy agregando 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 usa el operador de inserción de stringstream (str_stream << file_stream.rdbuf()). El problema es que esto establece el bit de error de stringstream cuando no se insertan caracteres. Eso puede deberse a un error o puede deberse a que el archivo está vacío. Si verifica si hay fallas al inspeccionar el bit de falla, encontrará un falso positivo cuando lea un archivo vacío. ¿Cómo desambigua la falla legítima al insertar caracteres y la “falla” al 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 verificación de errores asociada.

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 ostringstream ni en 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().

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

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad