php为了防止内存占用过多,在php.ini限制了内存占用,memory_limit = 16M默认为16M,可
以修改脚本占用内存限制,比如为-1,无限制。
做了如下实验。
场景:
一个1G左右的日志文件,大约700万行。
方案:
一、file函数直读。
代码如下
ini_set('memory_limit','-1');
$file = 'access.log';
$data = file($file);
$line = $data[count($data)-1];
echo $line;
机器直接无响应。。大概运行了200多秒。。
二、调用linux的tail函数
在linux命令行下,可以直接使用tail -n 10 access.log很轻易的显示日志文件某行。
file = 'access.log';
$file = escapeshellarg($file); // 对命令行参数进行安全转义
$line = `tail -n 1 $file`;
echo $line;
时间:0.0045秒,已经达到可用标准,但对环境有要求。
三、fseek函数
fseek函数不需要把文件全部装入内存,而是直接通过指针来操作,所以效率很高。代码:
$fp = fopen($file, "r");
$line = 10;
$pos = -2;
$t = " ";
$data = "";
while ($line > 0) {
while ($t != "\n") {
fseek($fp, $pos, SEEK_END);
$t = fgetc($fp);
$pos --;
}
$t = " ";
$data .= fgets($fp);
$line --;
}
fclose ($fp);
echo $data
时间:0.0105秒
再提升一点,按块读取,然后计算块内的\n标记来算到了多少行。
$fp = fopen($file, "r");
$num = 10;
$chunk = 4096;
$fs = sprintf("%u", filesize($file));
$max = (intval($fs) == PHP_INT_MAX) ? PHP_INT_MAX : filesize($file);
for ($len = 0; $len < $max; $len += $chunk) {
$seekSize = ($max - $len > $chunk) ? $chunk : $max - $len;
fseek($fp, ($len + $seekSize) * -1, SEEK_END);
$readData = fread($fp, $seekSize) . $readData;
if (substr_count($readData, "\n") >= $num + 1) {
preg_match("!(.*?\n){".($num)."}$!", $readData, $match);
$data = $match[0];
break;
}
}
fclose($fp);
echo $data;
时间:0.002秒
再快一点的话,可以判断如果行数大于一半,可以从后往前取,这样二分之后,取后面的速度会
更加快速。
function tail($fp,$n,$base=5)
{
assert($n>0);
$pos = $n+1;
$lines = array();
while(count($lines)< =$n){
try{
fseek($fp,-$pos,SEEK_END);
} catch (Exception $e){
fseek(0);
break;
}
$pos *= $base;
while(!feof($fp)){
array_unshift($lines,fgets($fp));
}
}
return array_slice($lines,0,$n);
}
var_dump(tail(fopen("access.log","r+"),10));
实际应用中再根据具体情况调整。