有效地计算文本文件的行数。 (200MB +)

时间:2010-01-29 14:26:11

标签: php file memory text memory-leaks

我刚刚发现我的剧本给了我一个致命的错误:

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;

所以我认为将文件加载到记忆中并计算行数很困难,有没有更有效的方法可以做到这一点而没有内存问题?

我需要计算的行数为2MB到500MB的文本文件。也许有时候是一个演出。

感谢大家的帮助。

17 个答案:

答案 0 :(得分:146)

这将使用更少的内存,因为它不会将整个文件加载到内存中:

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

答案 1 :(得分:99)

使用fgets()次调用循环是一个很好的解决方案,但编写起来最直接:

  1. 即使在内部使用8192字节的缓冲区读取文件,您的代码仍然必须为每一行调用该函数。

  2. 如果您正在阅读二进制文件,那么技术上可能只有一行可用内存。

  3. 此代码以每个8kB的块的形式读取一个文件,然后计算该块中的换行符数。

    function getLines($file)
    {
        $f = fopen($file, 'rb');
        $lines = 0;
    
        while (!feof($f)) {
            $lines += substr_count(fread($f, 8192), "\n");
        }
    
        fclose($f);
    
        return $lines;
    }
    

    如果每行的平均长度最多为4kB,则您已经开始保存函数调用,并且在处理大文件时可以加起来。

    基准

    我用1GB文件运行测试;结果如下:

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

    时间以秒为单位实时衡量,请参阅here真实含义

答案 2 :(得分:41)

简单导向对象解决方案

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

答案 3 :(得分:34)

如果您在Linux / Unix主机上运行此操作,最简单的解决方案是使用exec()或类似命令运行命令wc -l $path。只需确保首先清理$path以确保它不是“/ path / to / file; rm -rf /".

答案 4 :(得分:27)

我发现有一种更快的方法,不需要循环遍历整个文件

仅在* nix系统上

,在Windows上可能会有类似的方式......

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

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

答案 5 :(得分:8)

如果您使用的是PHP 5.5,则可以使用generator。这将 NOT 在5.5之前的任何版本的PHP中工作。来自php.net:

“生成器提供了一种简单的方法来实现简单的迭代器,而无需实现实现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

答案 6 :(得分:5)

这是对Wallace de Souza's解决方案的补充

计数时也会跳过空行:

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

答案 7 :(得分:3)

如果你在Linux下,你可以做到:

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

如果您正在使用其他操作系统,则必须找到正确的命令

此致

答案 8 :(得分:1)

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:)

玩得开心:)

答案 9 :(得分:1)

可以通过以下代码计算行数:

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

答案 10 :(得分:0)

最简洁的跨平台解决方案,一次只能缓冲一行。

$file = new \SplFileObject(__FILE__);
$file->setFlags($file::READ_AHEAD);
$lines = iterator_count($file);

不幸的是,我们必须设置READ_AHEAD标志,否则iterator_count会无限期地阻塞。否则,这将是单线的。

答案 11 :(得分:0)

基于多米尼克罗杰的解决方案, 这是我使用的(它使用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

答案 12 :(得分:0)

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

答案 13 :(得分:0)

还有另一个答案我认为可能是这个列表的一个很好的补充。

如果您已安装perl并且能够在PHP中运行shell中的内容:

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

这应该可以处理大多数换行符,无论是来自Unix还是Windows创建的文件。

两个缺点(至少):

1)让你的脚本依赖于运行的系统并不是一个好主意(假设Perl和wc可用是不安全的)

2)逃离时只是一个小错误,你已经移交了对机器上的shell的访问权。

就像我所知道的(或者我认为我知道的)关于编码的大多数事情一样,我从其他地方获得了这些信息:

John Reeve Article

答案 14 :(得分:0)

您有几种选择。第一种是增加允许的可用内存,这可能不是最好的方法,因为你声明文件可能变得非常大。另一种方法是使用fgets逐行读取文件并递增计数器,这根本不会导致任何内存问题,因为任何时候只有当前行在内存中。

答案 15 :(得分:-1)

仅计算线路使用次数:

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

答案 16 :(得分:-1)

我使用此方法纯粹计算文件中的行数。这样做的缺点是其他答案。我看到很多行而不是我的两行解决方案。我猜这是没有人这么做的原因。

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