有效地计算文本文件的行数。 (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和计数行数,有没有更有效的方式,我可以做到这一点,没有内存问题?
我需要对文本文件进行计数,范围从2MB到500MB。 有时候也许是Gig。
感谢所有的帮助。
这将使用较less的内存,因为它不会将整个文件加载到内存中:
$file="largefile.txt"; $linecount = 0; $handle = fopen($file, "r"); while(!feof($handle)){ $line = fgets($handle); $linecount++; } fclose($handle); echo $linecount;
fgets
将单行加载到内存中(如果省略了第二个参数$length
,它将继续读取stream,直到到达行的末尾,这就是我们想要的)。 如果你关心墙上的时间和内存的使用情况,那么这个速度还不如使用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()
调用循环是很好的解决scheme,而且最直接的写法是:
-
即使内部使用8192字节的缓冲区读取文件,您的代码仍然需要为每行调用该函数。
-
如果您正在读取二进制文件,则在技术上可行的是,一行可能比可用内存大。
该代码以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的文件进行了testing; 这里是结果:
+-------------+------------------+---------+ | 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/到/文件; rm -rf /”。
有一种更快的方式,我发现不需要循环整个文件
只在* nix系统上,窗口上可能有类似的方法…
$file = '/path/to/your.file'; //Get number of lines $totalLines = intval(exec("wc -l '$file'"));
简单的面向对象的解决scheme
$file = new \SplFileObject('file.extension'); while($file->valid()) $file->fgets(); var_dump($file->key());
更新
另一种方法是在SplFileObject::seek
方法中使用PHP_INT_MAX
。
$file = new \SplFileObject('file.extension', 'r'); $file->seek(PHP_INT_MAX); echo $file->key() + 1;
如果您使用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
这是Wallace de Souza解决scheme的补充
计数时还跳过空行:
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; }
你有几个select。 首先是增加可用的内存允许,这可能不是最好的办法做事情,因为你说的文件可以变得非常大。 另一种方法是使用fgets逐行读取文件,并增加一个计数器,这个计数器根本不应该引起任何内存问题,因为任何时候只有当前行在内存中。
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或不:)
玩的开心 :)
还有另一个答案,我认为这可能是一个很好的补充列表。
如果你已经安装了perl
,并且能够在PHP中运行shell:
$lines = exec('perl -pe \'s/\r\n|\n|\r/\n/g\' ' . escapeshellarg('largetextfile.txt') . ' | wc -l');
这应该处理大多数换行符,无论是从Unix或Windows创build的文件。
两个缺点(至less):
1)让你的脚本如此依赖于它运行的系统是不是一个好主意(假设Perl和wc可用是不安全的)
2)只是在逃跑的一个小错误,你已经交给你的机器上的shell访问。
就我所了解的大多数事情(或者认为我知道的)而言,我从别的地方得到了这个信息:
约翰里夫文章
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>"; }
为了计算这些线,使用:
$handle = fopen("file","r"); static $b = 0; while($a = fgets($handle)) { $b++; } echo $b;
基于罗杰多米诺的解决scheme,这里是我使用的(它使用wc,如果可用的话,否则回落到主宰罗杰的解决scheme)。
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; } }
我使用这个方法来纯粹地计算一个文件中有多less行。 做这个经文的其他答案的缺点是什么? 我看到很多行,而不是我的两行解决scheme。 我猜这是没有人做的。
$lines = count(file('your.file')); echo $lines;