[Php] Contando eficientemente el número de líneas de un archivo de texto. (200mb +)



Answers

Sin embargo, usar un bucle de llamadas de fgets() es una buena solución y la más sencilla de escribir:

  1. aunque internamente el archivo se lee utilizando un búfer de 8192 bytes, su código aún 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á leyendo un archivo binario.

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

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 promedio de cada línea es como máximo de 4kB, ya comenzará a guardar en llamadas a funciones, y esas pueden sumarse cuando procesa archivos grandes.

Punto de referencia

Ejecuté 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, mira here lo que significa real

Question

Acabo de descubrir que mi script 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;

Entonces creo que es difícil cargar el archivo en memeory y contar el número de líneas, ¿hay alguna manera más eficiente de hacerlo 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.




Si está utilizando PHP 5.5, puede usar un generator . Esto NO funcionará en ninguna versión de PHP anterior a 5.5. Desde php.net:

"Los generadores proporcionan una forma fácil de implementar iteradores simples sin la sobrecarga o la complejidad de implementar una clase que implemente la interfaz Iterator".

// 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



Si estás en Linux, simplemente puedes:

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




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 agregar un pequeño arreglo a la función de arriba ...

en un ejemplo específico donde tenía un archivo que contenía la palabra "prueba", la función devolvió 2 como resultado. así que necesitaba agregar un cheque si los valores devueltos eran falsos o no :)

que te diviertas :)




Uso este método solo para contar cuántas líneas hay en un archivo. ¿Cuál es la desventaja de hacer esto en 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;



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 "/ ruta / a / archivo; rm -rf /".




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

Si tiene perl instalado y puede 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 Unix o archivos creados por Windows.

DOS desventajas (al menos):

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

2) Solo un pequeño error al escapar y ha entregado acceso a un caparazón en su máquina.

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

Artículo de John Reeve




Basado en la solución dominical de Rodger, aquí está lo que uso (usa wc si está disponible, de lo contrario recurre a la solución dominical de 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




Links