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

Abs*_*Abs 81 php memory text memory-leaks file

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

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

那条线是这样的:

$lines = count(file($path)) - 1;
Run Code Online (Sandbox Code Playgroud)

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

我需要计算的行数为2MB到500MB的文本文件.也许有时候是Gig.

谢谢大家的帮助.

Dom*_*ger 152

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

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

fclose($handle);

echo $linecount;
Run Code Online (Sandbox Code Playgroud)

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;
Run Code Online (Sandbox Code Playgroud)

  • 不完美:你可以在windows机器上解析一个unix风格的文件(`\n`)(`PHP_EOL =='\ r \n'`) (4认同)
  • 这样返回的行数是否不会比行数多1?while(!feof())将使您多读一行,因为直到尝试读取文件末尾时才设置EOF指示器。 (3认同)
  • @DominicRodger在第一个例子中我相信`$line = fgets($handle);`可能只是`fgets($handle);`因为`$line`从未被使用过。 (2认同)

Ja͢*_*͢ck 103

使用循环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;
}
Run Code Online (Sandbox Code Playgroud)

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

基准

我用1GB的文件进行了测试; 结果如下:

             +-------------+------------------+---------+
             | This answer | Dominic's answer | wc -l   |
+------------+-------------+------------------+---------+
| Lines      | 3550388     | 3550389          | 3550388 |
+------------+-------------+------------------+---------+
| Runtime    | 1.055       | 4.297            | 0.587   |
+------------+-------------+------------------+---------+
Run Code Online (Sandbox Code Playgroud)

时间以秒为单位实时测量,请参见此处的真实含义

  • @OliCharlesworth他们平均超过五次跑,跳过第一次跑:) (6认同)
  • 小心这个基准,你先跑了吗?第二个将受益于文件已经在磁盘缓存中,大大扭曲了结果. (2认同)

Wal*_*ers 44

简单的面向对象解决方案

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

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

var_dump($file->key());
Run Code Online (Sandbox Code Playgroud)

更新

另一种方法是使用PHP_INT_MAXin SplFileObject::seek方法.

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

echo $file->key() + 1; 
Run Code Online (Sandbox Code Playgroud)

  • "更新"必须标记为已接受的答案! (4认同)
  • 第二个解决方案很棒,使用Spl!谢谢. (3认同)
  • 谢谢 !这确实很棒.并且比调用`wc -l`更快(因为我想要分叉),特别是在小文件上. (2认同)
  • 到目前为止,这是最好的解决方案 (2认同)
  • “key() + 1”对吗?我尝试了一下,似乎是错误的。对于给定文件,每一行(包括最后一行)都有行结尾,此代码给我 3998。但是如果我对其执行“wc”,我会得到 3997。如果我使用“vim”,它会显示 3997L(并且并不表示缺少停产)。所以我认为“更新”的答案是错误的。 (2认同)

Dav*_*man 34

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

  • @ ghostdog74:为什么,是的,你是对的.它是不便携的.这就是为什么我通过在"如果你在Linux/Unix主机上运行它......"这句话的前提下明确承认我的建议是不可移植的原因. (22认同)
  • @Manz:为什么,是的,你是对的.它是不便携的.这就是为什么我通过在"如果你在Linux/Unix主机上运行它......"这句话的前提下明确承认我的建议是不可移植的原因. (10认同)

And*_*ham 28

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

仅在*nix系统上,在Windows上可能有类似的方式......

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

//Get number of lines
$totalLines = intval(exec("wc -l '$file'"));
Run Code Online (Sandbox Code Playgroud)

  • `exec('wc -l'.escapeshellarg($ file).'2>/dev/null')` (5认同)

Ben*_*old 8

如果您使用的是PHP 5.5,则可以使用生成器.这不适用于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
Run Code Online (Sandbox Code Playgroud)

  • `try` /`finally`不是绝对必要的,PHP会自动关闭你的文件.您可能还应该提到实际计数可以使用`iterator_count(getFiles($ file))`:)来完成. (5认同)

小智 5

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

它还会在计数时跳过空行:

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; 
}
Run Code Online (Sandbox Code Playgroud)


elk*_*tfi 5

如果您使用的是Linux,则可以执行以下操作:

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

如果您使用的是其他操作系统,则只需找到正确的命令

问候