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

實現方法:php

1. 直接採用file函數來操做(不推薦)linux

注: 因爲 file函數是一次性將全部內容讀入內存,而php爲了防止一些寫的比較糟糕的程序佔用太多的內存而致使系統內存不足,使服務器出現宕機,因此默認狀況下 限制只能最大使用內存16M,這是經過php.ini裏的memory_limit = 16M來進行設置,這個值若是設置-1,則內存使用量不受限制.shell

下面是一段用file來取出這具文件最後一行的代碼.ubuntu

方法一:windows

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

$file = 'access.log';安全

$data = file($file);服務器

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

echo $line;ide


我機器是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行的結果

圖片


測試2

讀取10M文件

圖片


測試3

讀取10k

圖片

結果總結:

  • 方案五是大力推薦的,在各類大小的文件讀取中都有完美的表現;

  • 若是讀取10kb以上的文件,儘可能別使用第一種方法;

  • 方案二和方案三都不全面,測試中方案二的執行時間一直在2ms以上,而方案三存在大量的循環,會有比較大的性能影響(除非你只讀區一兩行)。

相關文章
相關標籤/搜索