Эффективный подсчет количества строк текстового файла. (200mb+)


Я только что узнал, что мой скрипт дает мне фатальная ошибка:

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

вот это:

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

поэтому я думаю, что у него возникли трудности с загрузкой файла в memeory и подсчетом количества строк, есть ли более эффективный способ сделать это без проблем с памятью?

текстовые файлы, которые мне нужно подсчитать количество строк для диапазона от 2 МБ до 500 МБ. Может быть, концерт иногда.

спасибо всем за любую помощь.

16 74

16 ответов:

это будет использовать меньше памяти, так как он не загружает весь файл в память.

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

fclose($handle);

echo $linecount;

fgets загружает одну строку в память (если второй аргумент $length опущен он будет продолжать чтение из потока, пока не достигнет конца строки, что мы и хотим). Это все еще вряд ли будет так же быстро, как использование чего-то другого, чем PHP, если вы заботитесь о времени стены, а также об использовании памяти.

единственная опасность с этим, если какие-либо линии особенно длинные (что делать, если вы столкнулись с файлом 2GB без разрывов строк?). В этом случае вам лучше делать глотание его кусками и подсчет символов конца строки:

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

используя цикл fgets() вызовы-это прекрасное решение и самое простое для записи, однако:

  1. хотя внутренне файл читается с использованием буфера 8192 байт, ваш код все равно должен вызывать эту функцию для каждой строки.

  2. технически возможно, что одна строка может быть больше, чем доступная память, если Вы читаете двоичный файл.

этот код считывает файл в куски по 8 КБ каждый, а затем подсчитывает количество новых строк в этом куске.

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

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

    fclose($f);

    return $lines;
}

если средняя длина каждой строки составляет не более 4 КБ, вы уже начнете экономить на вызовах функций, и они могут складываться при обработке больших файлов.

Benchmark

Я провел тест с файлом 1GB; вот результаты:

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

время измеряется в секундах реального времени, см. здесь что реально означает

Если вы используете это на хосте Linux / Unix, самым простым решением было бы использовать exec() или аналогично выполнить команду wc -l $path. Просто убедитесь, что вы санировали $path сначала убедитесь, что это не что-то вроде "/path/to/file ; rm-rf /".

есть более быстрый способ, который я нашел, что не требует цикла через весь файл

только в системах * nix, там может быть похожий способ на windows ...

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

//Get number of lines
$totalLines = intval(exec("wc -l '$file'"));

Если вы используете PHP 5.5, вы можете использовать генератор. Это будет не работа в любой версии PHP до 5.5, хотя. От php.net:

"генераторы обеспечивают простой способ реализации простых итераторов без накладных расходов или сложности реализации класса, реализующего интерфейс итератора."

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

Это дополнение к Уоллес Де Соуза решение

Он также пропускает пустые строки при подсчете:

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

Если вы находитесь под linux, вы можете просто сделать:

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

вы просто должны найти правильную команду, если вы используете другую ОС

в отношении

private static function lineCount($file) {
    $linecount = 0;
    $handle = fopen($file, "r");
    while(!feof($handle)){
        if (fgets($handle) !== false) {
                $linecount++;
        }
    }
    fclose($handle);
    return  $linecount;     
}

Я хотел добавить небольшое исправление к функции выше...

в конкретном примере, где у меня был файл, содержащий слово "testing", функция вернула 2 в результате. поэтому мне нужно было добавить проверку, если fgets вернул false или нет:)

удачи :)

подсчет количества строк можно выполнить с помощью следующих кодов:

<?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);
?>

У вас есть несколько вариантов. Во-первых, это увеличение доступной памяти, что, вероятно, не лучший способ сделать вещи, учитывая, что вы заявляете, что файл может получить очень большой. Другой способ-использовать fgets для чтения файла строка за строкой и увеличения счетчика, который не должен вызывать никаких проблем с памятью вообще, как только текущая строка находится в памяти в любой момент времени.

есть еще один ответ, который, как я думал, может быть хорошим дополнением к этому списку.

Если у вас perl установлены и могут запускать вещи из оболочки в PHP:

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

Это должно обрабатывать большинство разрывов строк, будь то из Unix или Windows, созданных файлов.

два недостатка (по крайней мере):

1) это не очень хорошая идея, чтобы ваш скрипт так зависел от системы, на которой он работает ( возможно, небезопасно предполагать, что Perl и wc доступно )

2) просто небольшая ошибка в побеге, и вы передали доступ к оболочке на вашей машине.

Как и большинство вещей, которые я знаю (или думаю, что знаю) о кодировании, я получил эту информацию откуда-то еще:

Статья Джона Рива

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

на основе решения Доминика Роджера, вот что я использую (он использует wc, если таковой имеется, в противном случае отступает к решению Доминика Роджера).

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

для простого подсчета строк используйте:

$handle = fopen("file","r");
static $b = 0;
while($a = fgets($handle)) {
    $b++;
}
echo $b;

Я использую этот метод для чисто подсчета количества строк в файле. В чем недостаток этого стиха другие ответы. Я вижу много линий в отличие от моих двух решение. Я предполагаю, что есть причина, по которой никто этого не делает.

$lines = count(file('your.file'));
echo $lines;