¿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.

adnan masood
fuente
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.
} 
codictorio
fuente
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);
}
Syuaa SE
fuente
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;
elshnkhll
fuente
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
@ Chud37 sí, hay: php.net/manual/en/splfileobject.eof.php
Nathan F.
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

Nino Škopac
fuente
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);
    ///
}
Starx
fuente
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";
}
NoImaginationGuy
fuente
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
19
foreach (new SplFileObject(__FILE__) as $line) {
    echo $line;
}
Preguntas de quolonel
fuente
Tengo que amar a los oneliners
Nino Škopac
1
Onestatementers.
Preguntas del Quolonel
1
Memoria eficiente en comparación con file().
Nobu
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);
Juan
fuente
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.

Cuse70
fuente
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);
Metodi Darzev
fuente
¿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);
}
Tegan Snyder
fuente
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;
}
xanadev
fuente
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++;
}
?>
Nguyễn Văn Cường
fuente
-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;
}
sixvel.com
fuente
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