Contar eficientemente el número de líneas de un archivo de texto. (200mb+)


Acabo de descubrir que mi guión me da un error fatal:

Fatal error: Allowed memory size of 268435456 bytes exhausted (tried to allocate 440 bytes) in C:\process_txt.php on line 109

Esa línea es esta:

$lines = count(file($path)) - 1;

Así que creo que está teniendo dificultades para cargar el archivo en memeory y contar el número de líneas, ¿hay una manera más eficiente que puedo hacer esto sin tener problemas de memoria?

Los archivos de texto que necesito para contar el número de líneas para el rango de 2MB a 500MB. Tal vez un concierto a veces.

Gracias a todos por cualquier ayuda.

Author: Abs, 2010-01-29

16 answers

Esto usará menos memoria, ya que no carga todo el archivo en memoria:

$file="largefile.txt";
$linecount = 0;
$handle = fopen($file, "r");
while(!feof($handle)){
  $line = fgets($handle);
  $linecount++;
}

fclose($handle);

echo $linecount;

fgets carga una sola línea en la memoria (si se omite el segundo argumento $length seguirá leyendo desde la secuencia hasta que llegue al final de la línea, que es lo que queremos). Todavía es poco probable que esto sea tan rápido como usar algo que no sea PHP, si te importa el tiempo de pared, así como el uso de memoria.

El único peligro con esto es si las líneas son particularmente largas (lo que si encuentra un archivo de 2 GB sin saltos de línea?). En cuyo caso es mejor hacer sorbiendo en trozos, y contando caracteres de final de línea:

$file="largefile.txt";
$linecount = 0;
$handle = fopen($file, "r");
while(!feof($handle)){
  $line = fgets($handle, 4096);
  $linecount = $linecount + substr_count($line, PHP_EOL);
}

fclose($handle);

echo $linecount;
 141
Author: Dominic Rodger,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-01-29 14:57:40

Usando un bucle de fgets() calls es una buena solución y la más sencilla de escribir, sin embargo:

  1. A pesar de que internamente el archivo se lee utilizando un búfer de 8192 bytes, su código todavía tiene que llamar a esa función para cada línea.

  2. Es técnicamente posible que una sola línea sea más grande que la memoria disponible si estás leyendo un archivo binario.

Este código lee un archivo en trozos de 8kB cada uno y luego cuenta el número de nuevas líneas dentro de ese trozo.

function getLines($file)
{
    $f = fopen($file, 'rb');
    $lines = 0;

    while (!feof($f)) {
        $lines += substr_count(fread($f, 8192), "\n");
    }

    fclose($f);

    return $lines;
}

Si la longitud media de cada línea es como máximo de 4 Kb, ya comenzará a guardar las llamadas a funciones, y esas pueden acumularse cuando procese archivos grandes.

Punto de referencia

Hice una prueba con un archivo de 1GB; aquí están los resultados:

             +-------------+------------------+---------+
             | This answer | Dominic's answer | wc -l   |
+------------+-------------+------------------+---------+
| Lines      | 3550388     | 3550389          | 3550388 |
+------------+-------------+------------------+---------+
| Runtime    | 1.055       | 4.297            | 0.587   |
+------------+-------------+------------------+---------+

El tiempo se mide en segundos en tiempo real, ver aquí lo que significa real

 97
Author: Ja͢ck,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:26:32

Solución de Objeto Orientado simple

$file = new \SplFileObject('file.extension');

while($file->valid()) $file->fgets();

var_dump($file->key());

Actualizar

Otra forma de hacer esto es con PHP_INT_MAX en el método SplFileObject::seek.

$file = new \SplFileObject('file.extension', 'r');
$file->seek(PHP_INT_MAX);

echo $file->key() + 1; 
 38
Author: Wallace Maxters,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-01-16 19:30:22

Si está ejecutando esto en un host Linux/Unix, la solución más fácil sería usar exec() o similar para ejecutar el comando wc -l $path. Solo asegúrese de haber desinfectado $path primero para asegurarse de que no es algo como "/path/to/file ; rm-rf /".

 34
Author: Dave Sherohman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-01-29 14:30:03

Hay una forma más rápida que encontré que no requiere bucear a través de todo el archivo

Solo en * nix systems , podría haber una forma similar en Windows ...

$file = '/path/to/your.file';

//Get number of lines
$totalLines = intval(exec("wc -l '$file'"));
 26
Author: Andy Braham,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-11-11 17:11:18

Si estás usando PHP 5.5 puedes usar un generador . Esto NO funcionará en ninguna versión de PHP anterior a la 5.5. Desde php.net:

"Los generadores proporcionan una manera fácil de implementar iteradores simples sin la sobrecarga o complejidad de implementar una clase que implementa la interfaz del Iterador."

// This function implements a generator to load individual lines of a large file
function getLines($file) {
    $f = fopen($file, 'r');

    // read each line of the file without loading the whole file to memory
    while ($line = fgets($f)) {
        yield $line;
    }
}

// Since generators implement simple iterators, I can quickly count the number
// of lines using the iterator_count() function.
$file = '/path/to/file.txt';
$lineCount = iterator_count(getLines($file)); // the number of lines in the file
 8
Author: Ben Harold,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-12-12 16:11:12

Esta es una adición a solución de Wallace de Souza

También salta líneas vacías mientras cuenta:

function getLines($file)
{
    $file = new \SplFileObject($file, 'r');
    $file->setFlags(SplFileObject::READ_AHEAD | SplFileObject::SKIP_EMPTY | 
SplFileObject::DROP_NEW_LINE);
    $file->seek(PHP_INT_MAX);

    return $file->key() + 1; 
}
 5
Author: Jani,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-28 07:10:49

Si estás bajo Linux simplemente puedes hacer:

number_of_lines = intval(trim(shell_exec("wc -l ".$file_name." | awk '{print $1}'")));

Solo tienes que encontrar el comando correcto si estás usando otro sistema operativo

Saludos

 2
Author: epixilog,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-25 08:47:11
private static function lineCount($file) {
    $linecount = 0;
    $handle = fopen($file, "r");
    while(!feof($handle)){
        if (fgets($handle) !== false) {
                $linecount++;
        }
    }
    fclose($handle);
    return  $linecount;     
}

Quería añadir una pequeña corrección a la función anterior...

En un ejemplo específico donde tenía un archivo que contenía la palabra 'testing' la función devolvió 2 como resultado. así que necesité agregar una comprobación si fgets devolvió false o no:)

Diviértete:)

 1
Author: ufk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-01-30 07:38:07

Contando el número de líneas se puede hacer mediante los siguientes códigos:

<?php
$fp= fopen("myfile.txt", "r");
$count=0;
while($line = fgetss($fp)) // fgetss() is used to get a line from a file ignoring html tags
$count++;
echo "Total number of lines  are ".$count;
fclose($fp);
?>
 1
Author: Santosh Kumar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-04-02 14:58:04

Tiene varias opciones. La primera es aumentar la memoria disponible permitida, que probablemente no es la mejor manera de hacer las cosas dado que afirma que el archivo puede ser muy grande. La otra forma es usar fgets para leer el archivo línea por línea e incrementar un contador, lo que no debería causar ningún problema de memoria ya que solo la línea actual está en memoria en un momento dado.

 0
Author: Yacoby,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-01-29 14:31:49

Hay otra respuesta que pensé que podría ser una buena adición a esta lista.

Si tiene perl instalado y es capaz de ejecutar cosas desde el shell en PHP:

$lines = exec('perl -pe \'s/\r\n|\n|\r/\n/g\' ' . escapeshellarg('largetextfile.txt') . ' | wc -l');

Esto debería manejar la mayoría de los saltos de línea, ya sea desde archivos creados en Unix o Windows.

DOS desventajas (al menos):

1) No es una gran idea tener su script tan dependiente del sistema en el que se ejecuta (puede que no sea seguro asumir que Perl y wc están disponibles )

2) Solo un pequeño error en escapar y que han entregado el acceso a un shell en su máquina.

Al igual que con la mayoría de las cosas que sé (o creo que sé) sobre codificación, obtuve esta información de otro lugar:

Artículo de John Reeve

 0
Author: Douglas.Sesar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-08-02 23:45:24
public function quickAndDirtyLineCounter()
{
    echo "<table>";
    $folders = ['C:\wamp\www\qa\abcfolder\',
    ];
    foreach ($folders as $folder) {
        $files = scandir($folder);
        foreach ($files as $file) {
            if($file == '.' || $file == '..' || !file_exists($folder.'\\'.$file)){
                continue;
            }
                $handle = fopen($folder.'/'.$file, "r");
                $linecount = 0;
                while(!feof($handle)){
                    if(is_bool($handle)){break;}
                    $line = fgets($handle);
                    $linecount++;
                  }
                fclose($handle);
                echo "<tr><td>" . $folder . "</td><td>" . $file . "</td><td>" . $linecount . "</td></tr>";
            }
        }
        echo "</table>";
}
 0
Author: Yogi Sadhwani,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-08-28 21:02:52

Basado en la solución de dominic Rodger, esto es lo que uso (usa wc si está disponible, de lo contrario, recurre a la solución de Dominic Rodger).

class FileTool
{

    public static function getNbLines($file)
    {
        $linecount = 0;

        $m = exec('which wc');
        if ('' !== $m) {
            $cmd = 'wc -l < "' . str_replace('"', '\\"', $file) . '"';
            $n = exec($cmd);
            return (int)$n + 1;
        }


        $handle = fopen($file, "r");
        while (!feof($handle)) {
            $line = fgets($handle);
            $linecount++;
        }
        fclose($handle);
        return $linecount;
    }
}

Https://github.com/lingtalfi/Bat/blob/master/FileTool.php

 0
Author: ling,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-12-23 19:48:08

Para simplemente contar las líneas use:

$handle = fopen("file","r");
static $b = 0;
while($a = fgets($handle)) {
    $b++;
}
echo $b;
 -1
Author: Adeel Ahmad,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-02-19 15:28:25

Utilizo este método para contar puramente cuántas líneas hay en un archivo. ¿Cuál es la desventaja de hacer este versículo las otras respuestas. Estoy viendo muchas líneas en lugar de mi solución de dos líneas. Supongo que hay una razón por la que nadie hace esto.

$lines = count(file('your.file'));
echo $lines;
 -1
Author: kaspirtk1,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-10-26 14:24:43