php统计文本行数几种文件性能对比.
类似linux提供wc -l 命令一样,统计文本行数.
注意点:
- 文件大小
- 内存大小
准备工作
php版本:7.4
操作系统: Macos10.15.7
配置: 16GB 3.1 GHz 双核Intel Core i7
生成文本:
// 生成1k随机文本
dd if=/dev/random of=1k.txt bs=1k count=1
// 1m
dd if=/dev/random of=1mb.txt bs=1m count=1
// 100MB
dd if=/dev/random of=100m.txt bs=1m count=100
// 1G的文本
dd if=/dev/random of=1g.txt bs=1m count=1024
// 4G
dd if=/dev/random of=4g.txt bs=1g count=4
// 10G
dd if=/dev/random of=10g.txt bs=1g count=10
如下
测试代码
<?php declare(strict_types=1);
ini_set('memory_limit','-1');
Trace::Start();
Trace::Log('前'); // 统计内存和时间
// 运行代码
$lines=1000;
var_dump($lines);
Trace::Log('后');
Trace::End();
?>
对比调用wc命令:
function wc($file){
$command = 'wc -l '.realpath($file).' | awk \'{print $1}\' ';
return intval(shell_exec($command))+1;
}
参考内存和时间:
ver_dump 内存占用:1.35MB 耗时0.0001秒 ,后面以这个为参考标准.
计算执行时间:
time php wc.php
为
php wc.php 0.07s user 0.06s system 95% cpu 0.138 total
参考wc命令执行结果. 注意wc -l 统计比实际少1行.
方案1:
使用file函数.file()
:把整个文件按行读入一个数组中.
优点: 代码量少
缺点: 占用内存比较高
function countLine1(string $file):int{
return count(file($file));
}
- 1k文件
wc -l data/1k.txt
2行,耗时: 0.003
time php wc.php
3行,耗时: 0.136,内存:2MB
- 1MB
[wc]
4000行,耗时0.005秒
[php]
4000行,0.133秒,内存4MB ,1MB文件可以接受范围内.
结论,内存128MB可使用50MB文件.时间在200毫秒左右.
如果大于1大于内存就不适合了.
第二种方法
fget: 逐行读取.
注意: fget的默认最大长度为1024字节,在长度范围内如果遇到换行符就返回,如果超过1024字节没有换行符就会新起一行.
function countLine2(string $file):int{
$count=0;
try {
$fp = fopen($file, 'r');
while (!feof($fp)){
$buff=fgets($fp,1024);
if($buff!==false && $buff[-1]=="\n"){
$count++;
}
}
}
finally {
if(is_resource($fp)){
fclose($fp);
}
return $count;
}
}
执行效率:
1MB文件,4000行,耗时0.13秒,内存2MB
1G文件,耗时1.093秒,内存2MB
4G文件,与wc相差1秒.
结论: 无论多大的文件都不会占用太多内存,但性能比wc差.
主要是按行切割,寻找换行符.
第三种方案
使用fread分块读取,然后在substr_count统计换行符数量. 根据内容
function countLine3($file)
{
$i = 0;
try {
$fp = fopen($file, "r");
while (!feof($fp)) {
if ($data = fread($fp, 1024 * 100)) {
$num = substr_count($data, "\n");
$i += $num;
}
}
fclose($fp);
}finally {
if(is_resource($fp)){
fclose($fp);
}
}
return $i;
}
1K时
1G时,内存一样,耗时减半.
与wc -l 相比时间减少1/3.
结论: fread 自定义分段读取内容,然后在统计换行符数量.
每行字符数量很少时,可以一次性读取很多行,而不是逐行读取,在内存中计算那换行数量,减少文件io.
第4种,SplFileObject
第二种基础上,有更简单的标准文件处理.
优缺点:
优点有更少的代码量,不需要打开文件和关闭文件.
逐行扫描,注意必须要current.
function countLine4(string $file){
$fp=new SplFileObject($file);
$count=0;
while (!$fp->eof()){
$fp->current();
$count++;
$fp->next();
}
return $count;
}
通过foreach 迭代:
function countLine4(string $file){
$fp=new SplFileObject($file);
$count=0;
foreach($fp as $line){
$count++;
}
return $count;
}
通过内置跳转到最后一行,获取key+1.
seek参数是行,由于不知道最后一行是多大,我们取字节数getSize()
或php最大数PHP_INT_MAX
,超过实际行数就会跳转到最后一行.
function countLine4(string $file){
$fp=new SplFileObject($file,'r');
// 跳转到最大行
$fp->seek(PHP_INT_MAX);
// 或者跳到字节数
// $fp->seek($fp->getSize());
// 返回当前key,即是行数
return $fp->key()+1;
}
性能对比
内存2MB,单位秒
方案/大小 | 1kb | 1MB | 1G | 10G |
---|---|---|---|---|
wc -l | 0.004 | 0.005 | 1.003 | 10.490 |
file() | 0.0006 | 0.004s | x | x |
fgets() | 0.0002 | 0.0023 | 0.9945s | 12.7513 |
fread() | 0.0002 | 0.0009s | 0.4039 | 6.7676 |
SplFileObject::foreach | 0.0002 | 0.0015s | 1.2329 | 13.8505 |
SplFileObject::next | 0.0002 | 0.0012 | 0.9976 | 11.7744 |
SplFileObject::seek | 0.0002 | 0.0008 | 0.5888 | 7.6388 |
结论:
性能最好的是 fread和 splFile的seek
最简单的使用,file和splFile的foreach
在对比PHP的性能提升: php8性能提升不明显,0.00几秒,内存占用减少.
建议使用spl方式处理文件.
附加php8.1内存使用.
原作者:阿金
本文地址:https://hi-arkin.com/archives/php-wc.html