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

2022-08-30 07:31:46

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

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中并计算行数,有没有更有效的方法可以做到这一点而不会出现内存问题?

我需要计算行数的文本文件范围从2MB到500MB。有时也许是一场演出。

感谢大家的任何帮助。


答案 1

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

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

fclose($handle);

echo $linecount;

fgets将一行加载到内存中(如果省略第二个参数,它将继续从流中读取,直到它到达行的末尾,这就是我们想要的)。这仍然不太可能像使用PHP以外的其他东西一样快,如果你关心墙壁时间和内存使用情况。$length

唯一的危险是,如果任何行特别长(如果您遇到没有换行符的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;

答案 2

但是,使用 fgets() 调用循环是很好的解决方案,也是最直接的编写方法:

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

  2. 从技术上讲,如果您正在读取二进制文件,则单个行可能大于可用内存。

此代码以每个 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   |
+------------+-------------+------------------+---------+

时间以秒为单位实时测量,在这里看看真正的意味着什么

真实行数

虽然上述方法运行良好,并且返回的结果与 相同,但如果文件结尾没有换行符,则行号将偏离一个;如果您关心此特定方案,则可以使用以下逻辑使其更准确:wc -l


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

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

    fclose($f);

    if (strlen($buffer) > 0 && $buffer[-1] != "\n") {
        ++$lines;
    }
    return $lines;
}


推荐