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


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

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 МБ. Может быть, иногда на концерте.

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

Author: Abs, 2010-01-29

16 answers

Это займет меньше памяти, так как не загружает весь файл в память:

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

fclose($handle);

echo $linecount;

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

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

$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, 2010-01-29 14:57:40

Используя цикл 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 Кб, вы уже начнете экономить на вызовах функций, и они могут накапливаться при обработке больших файлов.

Эталонный показатель

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

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

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

 97
Author: Ja͢ck, 2017-05-23 12:26:32

Простое ориентированное объектное решение

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

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

var_dump($file->key());

Обновление

Другой способ сделать это - с помощью метода PHP_INT_MAX в SplFileObject::seek.

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

echo $file->key() + 1; 
 38
Author: Wallace Maxters, 2016-01-16 19:30:22

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

 34
Author: Dave Sherohman, 2010-01-29 14:30:03

Я нашел более быстрый способ, который не требует циклического просмотра всего файла

Только в системах *nix может быть аналогичный способ в Windows...

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

//Get number of lines
$totalLines = intval(exec("wc -l '$file'"));
 26
Author: Andy Braham, 2013-11-11 17:11:18

Если вы используете 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
 8
Author: Ben Harold, 2013-12-12 16:11:12

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

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

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, 2017-06-28 07:10:49

Если вы работаете под Linux, вы можете просто сделать:

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

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

С уважением

 2
Author: epixilog, 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;     
}

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

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

Получайте удовольствие:)

 1
Author: ufk, 2013-01-30 07:38:07

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

<?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, 2018-04-02 14:58:04

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

 0
Author: Yacoby, 2010-01-29 14:31:49

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

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

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

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

ДВА недостатка (по крайней мере):

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

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

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

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

 0
Author: Douglas.Sesar, 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, 2014-08-28 21:02:52

Основываясь на решении Доминика Роджера, вот что я использую (он использует 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

 0
Author: ling, 2016-12-23 19:48:08

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

$handle = fopen("file","r");
static $b = 0;
while($a = fgets($handle)) {
    $b++;
}
echo $b;
 -1
Author: Adeel Ahmad, 2015-02-19 15:28:25

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

$lines = count(file('your.file'));
echo $lines;
 -1
Author: kaspirtk1, 2017-10-26 14:24:43