php中读取大文件方案及性能分析

实现方法:

1. 直接采用file函数来操作(不推荐)

注: 由于 file函数是一次性将所有内容读入内存,而php为了防止一些写的比较糟糕的程序占用太多的内存而导致系统内存不足,使服务器出现宕机,所以默认情况下 限制只能最大使用内存16M,这是通过php.ini里的memory_limit = 16M来进行设置,这个值如果设置-1,则内存使用量不受限制.

下面是一段用file来取出这具文件最后一行的代码.

方法一:

ini_set('memory_limit','-1');

$file = 'access.log';

$data = file($file);

$line = $data[count($data)-1];

echo $line;


我机器是2个G的内存,当按下F5运行时,系统直接变灰,差不多20分钟后才恢复过来,可见将这么大的文件全部直接读入内存,后果是多少严重,所以不在万不得以,memory_limit这东西不能调得太高,否则只有打电话给机房,让reset机器了.


2.直接调用linux的tail命令来显示最后几行(不推荐)

在linux命令行下,可以直接使用tail -n 10 access.log很轻易的显示日志文件最后几行,可以直接用php来调用tail命令,执行php代码如下.


方法二:

file = 'access.log';

$file = escapeshellarg($file); // 对命令行参数进行安全转义

$line = `tail -n 1 $file`;

echo $line; 

该种方法也存在很大的局限型,对于不支持tail命令的机器不支持该方案(windows)


3. 直接使用php的fseek来进行文件操作

这种方式是最为普遍的方式,它不需要将文件的内容全部读入内存,而是直接通过指针来操作,所以效率是相当高效的.在使用fseek来对文件进行操作时,也有多种不同的方法,效率可能也是略有差别的,下面是常用的两种方法.

方法三:

首先通过fseek找到文件的最后一位EOF,然后找最后一行的起始位置,取这一行的数据,再找次一行的起始位置,再取这一行的位置,依次类推,直到找到了$num行。

实现代码如下

function read_file($file, $lines)
{
       $handle = fopen($file, "r");
       $linecounter = $lines;
       $pos = -2;
       $beginning = false;
       $text = array();
       while ($linecounter > 0) {
         $t = " ";
         while ($t != "\n") {
           if(fseek($handle, $pos, SEEK_END) == -1) {
$beginning = true; break; }
           $t = fgetc($handle);
           $pos --;
         }
         $linecounter --;
         if($beginning) rewind($handle);
         $text[$lines-$linecounter-1] = fgets($handle);
         if($beginning break;
       }
       fclose ($handle);
       return array_reverse($text); // array_reverse is optional: you can also just return the $text array which consists of the file's lines. 
}


方法四:

还是采用fseek的方式从文件最后开始读,但这时不是一位一位的读,而是一块一块的读,每读一块数据时,就将读取后的数据放在一个buf里,然后通过换行符(n)的个数来判断是否已经读完最后$num行数据.

实现代码如下

function tail($filename, $lines = 10, $buffer = 4096)

{

    // Open the file

    $f = fopen($filename, "rb");


    // Jump to last character

    fseek($f, -1, SEEK_END);


    // Read it and adjust line number if necessary

    // (Otherwise the result would be wrong if file doesn't end with a blank line)

    if(fread($f, 1) != "\n") $lines -= 1;


    // Start reading

    $output = '';

    $chunk = '';


    // While we would like more

    while(ftell($f) > 0 && $lines >= 0)

    {

        // Figure out how far back we should jump

        $seek = min(ftell($f), $buffer);


        // Do the jump (backwards, relative to where we are)

        fseek($f, -$seek, SEEK_CUR);


        // Read a chunk and prepend it to our output

        $output = ($chunk = fread($f, $seek)).$output;


        // Jump back to where we started reading

        fseek($f, -mb_strlen($chunk, '8bit'), SEEK_CUR);


        // Decrease our line counter

        $lines -= substr_count($chunk, "\n");

    }


    // While we have too many lines

    // (Because of buffer size we might have read too many)

    while($lines++ < 0)

    {

        // Find first newline and remove all text before that

        $output = substr($output, strpos($output, "\n") + 1);

    }


    // Close file and return

    fclose($f); 

    return $output; 

}



方法五:

方法二的修改版,缓存长度是根据当前行总数动态变化的。

function tailCustom($filepath, $lines = 1, $adaptive = true) {

// Open file

$f = @fopen($filepath, "rb");

if ($f === false) return false;

// Sets buffer size

if (!$adaptive) $buffer = 4096;

else $buffer = ($lines < 2 ? 64 : ($lines < 10 ? 512 : 4096));

// Jump to last character

fseek($f, -1, SEEK_END);

// Read it and adjust line number if necessary

// (Otherwise the result would be wrong if file doesn't end with a blank line)

if (fread($f, 1) != "\n") $lines -= 1;

// Start reading

$output = '';

$chunk = '';

// While we would like more

while (ftell($f) > 0 && $lines >= 0) {

// Figure out how far back we should jump

$seek = min(ftell($f), $buffer);

// Do the jump (backwards, relative to where we are)

fseek($f, -$seek, SEEK_CUR);

// Read a chunk and prepend it to our output

$output = ($chunk = fread($f, $seek)) . $output;

// Jump back to where we started reading

fseek($f, -mb_strlen($chunk, '8bit'), SEEK_CUR);

// Decrease our line counter

$lines -= substr_count($chunk, "\n");

}

// While we have too many lines

// (Because of buffer size we might have read too many)

while ($lines++ < 0) {

// Find first newline and remove all text before that

$output = substr($output, strpos($output, "\n") + 1);

}

// Close file and return

fclose($f);

return trim($output);

}



对以上五种方法的性能测试

测试机配置:Xubuntu 12.04, PHP 5.3.10, 2.70 GHz dual core CPU, 2 GB RAM


测试1

从一个100kb的文件结尾读取文件的第1, 2, .., 10, 20, ... 100, 200, ..., 1000行的结果

php中读取大文件方案及性能分析


测试2

读取10M文件

php中读取大文件方案及性能分析


测试3

读取10k

php中读取大文件方案及性能分析

结果总结:

  • 方案五是大力推荐的,在各种大小的文件读取中都有完美的表现;

  • 如果读取10kb以上的文件,尽量别使用第一种方法;

  • 方案二和方案三都不全面,测试中方案二的执行时间一直在2ms以上,而方案三存在大量的循环,会有比较大的性能影响(除非你只读区一两行)。


上一篇:标准正弦波变频电源调制方式的实现


下一篇:PHP大文件上传支持断点上传教程