¿Cómo leer un archivo grande línea por línea?


470

Quiero leer un archivo línea por línea, pero sin cargarlo completamente en la memoria.

Mi archivo es demasiado grande para abrirlo en la memoria, y si trato de hacerlo, siempre me quedo sin errores de memoria.

El tamaño del archivo es de 1 GB.


mira mi respuesta en este enlace
Sohail Ahmed

77
Debe usar fgets()sin $lengthparámetro.
Carlos

26
¿Desea marcar como respuesta en cualquiera de los siguientes?
Kim Stacks

Respuestas:


685

Puede usar la fgets()función para leer el archivo línea por línea:

$handle = fopen("inputfile.txt", "r");
if ($handle) {
    while (($line = fgets($handle)) !== false) {
        // process the line read.
    }

    fclose($handle);
} else {
    // error opening the file.
} 

3
¿Cómo explica esto la too large to open in memoryparte?
Starx

64
No está leyendo todo el archivo en la memoria. La memoria máxima necesaria para ejecutar esto depende de la línea más larga en la entrada.
codaddict

13
@Brandin - Moot - En esas situaciones, la pregunta formulada, que es leer un archivo LÍNEA A LÍNEA, no tiene un resultado bien definido.
ToolmakerSteve

3
@ToolmakerSteve Luego defina lo que debe suceder. Si lo desea, puede imprimir el mensaje "Línea demasiado larga; darse por vencido". y ese es un resultado bien definido también.
Brandin

2
¿Puede una línea contener un booleano falso? Si es así, este método se detendría sin llegar al final del archivo. El Ejemplo # 1 en esta URL php.net/manual/en/function.fgets.php sugiere que los fgets a veces pueden devolver boolean false aunque aún no se haya alcanzado el final del archivo. En la sección de comentarios de esa página, las personas informan que fgets () no siempre devuelve los valores correctos, por lo que es más seguro usar feof como condicional de bucle.
cjohansson

131
if ($file = fopen("file.txt", "r")) {
    while(!feof($file)) {
        $line = fgets($file);
        # do same stuff with the $line
    }
    fclose($file);
}

8
Como dijo @ Cuse70 en su respuesta, esto conducirá a un bucle infinito si el archivo no existe o no se puede abrir. Prueba if($file)antes del ciclo while
FrancescoMM

10
Sé que esto es viejo, pero: no se recomienda usar while (! Feof ($ file)). Echa un vistazo aquí.
Kevin Van Ryckegem

Por cierto: "Si no hay más datos para leer en el puntero del archivo, se devuelve FALSE". php.net/manual/en/function.fgets.php ... Por si acaso
Everyman

2
feof()ya no existe?
Ryan DuVal

94

Puede usar una clase de interfaz orientada a objetos para un archivo: SplFileObject http://php.net/manual/en/splfileobject.fgets.php (PHP 5> = 5.1.0)

<?php

$file = new SplFileObject("file.txt");

// Loop until we reach the end of the file.
while (!$file->eof()) {
    // Echo one line from the file.
    echo $file->fgets();
}

// Unset the file to call __destruct(), closing the file handle.
$file = null;

3
Solución mucho más limpia. gracias;) no he usado esta clase todavía, hay más funciones interesantes aquí para explorar: php.net/manual/en/class.splfileobject.php
Lukas Liesis

66
Gracias. Sí, por ejemplo, puede agregar esta línea antes mientras $ file-> setFlags (SplFileObject :: DROP_NEW_LINE); para soltar nuevas líneas al final de una línea.
elshnkhll

Por lo que puedo ver, ¿no hay ninguna eof()función en SplFileObject?
Chud37

3
¡Gracias! Además, use rtrim($file->fgets())para quitar las nuevas líneas finales para cada cadena de línea que se lee si no las desea.
racl101


59

Si está abriendo un archivo grande, probablemente quiera usar Generadores junto con fgets () para evitar cargar todo el archivo en la memoria:

/**
 * @return Generator
 */
$fileData = function() {
    $file = fopen(__DIR__ . '/file.txt', 'r');

    if (!$file)
        die('file does not exist or cannot be opened');

    while (($line = fgets($file)) !== false) {
        yield $line;
    }

    fclose($file);
};

Úselo así:

foreach ($fileData() as $line) {
    // $line contains current line
}

De esta forma, puede procesar líneas de archivo individuales dentro de foreach ().

Nota: los generadores requieren> = PHP 5.5


3
Esta debería ser una respuesta aceptada en su lugar. Es cien veces más rápido con generadores.
Tachi

1
Y mucho más eficiente en memoria.
Nino Škopac

2
@ NinoŠkopac: ¿Puede explicar por qué esta solución es más eficiente en memoria? Por ejemplo, en comparación con el SplFileObjectenfoque.
k00ni

30

Use técnicas de almacenamiento en búfer para leer el archivo.

$filename = "test.txt";
$source_file = fopen( $filename, "r" ) or die("Couldn't open $filename");
while (!feof($source_file)) {
    $buffer = fread($source_file, 4096);  // use a buffer of 4KB
    $buffer = str_replace($old,$new,$buffer);
    ///
}

2
esto merece más amor, ya que funcionará con archivos enormes, incluso archivos que no tienen retornos de carro o líneas extremadamente largas ...
Jimmery

No me sorprendería si el OP realmente no se preocupara por las líneas reales y solo quisiera, por ejemplo, publicar una descarga. En ese caso, esta respuesta está bien (y lo que la mayoría de los codificadores de PHP harían de todos modos).
Álvaro González el

30

Hay una file()función que devuelve una matriz de líneas contenidas en el archivo.

foreach(file('myfile.txt') as $line) {
   echo $line. "\n";
}

28
El archivo de un GB se leería todo en la memoria y se convertiría en una matriz de más de un GB ... buena suerte.
FrancescoMM

44
Esta no fue la respuesta a la pregunta formulada, pero sí responde a la pregunta más común que muchas personas tienen al mirar aquí, por lo que fue útil, gracias.
pilavdzice

2
file () es muy conveniente para trabajar con archivos pequeños. Especialmente cuando quieres una matriz () como resultado final.
functionvoid

esta es una mala idea con archivos más grandes ya que todo el archivo se está leyendo en una matriz a la vez
Flash Thunder

Esto se rompe mal en archivos grandes, por lo que es exactamente el método que no funciona.
ftrotter


17

La respuesta obvia no estaba allí en todas las respuestas.
PHP tiene un analizador delimitador de transmisión ordenado disponible hecho exactamente para ese propósito.

$fp = fopen("/path/to/the/file", "r+");
while ($line = stream_get_line($fp, 1024 * 1024, "\n")) {
  echo $line;
}
fclose($fp);

Cabe señalar que este código devolverá solo líneas hasta que ocurra la primera línea vacía. Necesita probar $ line! == false en la condición while (($line = stream_get_line($fp, 1024 * 1024, "\n")) !== false)
while

8

Tenga cuidado con las cosas 'while (! Feof ... fgets ()', los fgets pueden obtener un error (returnfing false) y repetirse para siempre sin llegar al final del archivo. Codaddict estuvo más cerca de ser correcto pero cuando su 'while fgets' el ciclo termina, verifique feof; si no es cierto, entonces tuvo un error.


8

Así es como lo manejo con un archivo muy grande (probado con hasta 100G). Y es más rápido que fgets ()

$block =1024*1024;//1MB or counld be any higher than HDD block_size*2
if ($fh = fopen("file.txt", "r")) { 
    $left='';
    while (!feof($fh)) {// read the file
       $temp = fread($fh, $block);  
       $fgetslines = explode("\n",$temp);
       $fgetslines[0]=$left.$fgetslines[0];
       if(!feof($fh) )$left = array_pop($lines);           
       foreach ($fgetslines as $k => $line) {
           //do smth with $line
        }
     }
}
fclose($fh);

¿Cómo se asegura de que el bloque 1024 * 1024 no se rompa en el medio de la línea?
user151496

1
@ user151496 fácil !! contar ... 1.2.3.4
Omar El Don

@OmarElDon, ¿qué quieres decir?
Codex73

7

Una de las soluciones populares a esta pregunta tendrá problemas con el nuevo carácter de línea. Se puede arreglar bastante fácil con un simple str_replace.

$handle = fopen("some_file.txt", "r");
if ($handle) {
    while (($line = fgets($handle)) !== false) {
        $line = str_replace("\n", "", $line);
    }
    fclose($handle);
}

6

SplFileObject es útil cuando se trata de manejar archivos grandes.

function parse_file($filename)
{
    try {
        $file = new SplFileObject($filename);
    } catch (LogicException $exception) {
        die('SplFileObject : '.$exception->getMessage());
    }
    while ($file->valid()) {
        $line = $file->fgets();
        //do something with $line
    }

    //don't forget to free the file handle.
    $file = null;
}

1
<?php
echo '<meta charset="utf-8">';

$k= 1;
$f= 1;
$fp = fopen("texttranslate.txt", "r");
while(!feof($fp)) {
    $contents = '';
    for($i=1;$i<=1500;$i++){
        echo $k.' -- '. fgets($fp) .'<br>';$k++;
        $contents .= fgets($fp);
    }
    echo '<hr>';
    file_put_contents('Split/new_file_'.$f.'.txt', $contents);$f++;
}
?>

-8

Función para leer con retorno de matriz

function read_file($filename = ''){
    $buffer = array();
    $source_file = fopen( $filename, "r" ) or die("Couldn't open $filename");
    while (!feof($source_file)) {
        $buffer[] = fread($source_file, 4096);  // use a buffer of 4KB
    }
    return $buffer;
}

44
Esto crearía una única matriz de más de un GB en memoria (buena suerte con ella) dividida ni siquiera en líneas sino en fragmentos arbitrarios de 4096 caracteres. ¿Por qué demonios querrías hacer eso?
FrancescoMM
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.