2011-03-09 69 views
16

我在處理大文件時沒有經驗,所以我不知道該怎麼做。我試圖使用file_get_contents來讀取幾個大文件;任務是使用preg_replace()進行清理並將它們打包。file_get_contents => PHP致命錯誤:允許內存耗盡

我的代碼在小文件上運行良好;但是,大文件(40 MB)觸發內存耗盡錯誤:

PHP Fatal error: Allowed memory size of 16777216 bytes exhausted (tried to allocate 41390283 bytes) 

我想用FREAD()代替,但我不知道會擦出火花。有沒有解決這個問題的方法?

感謝您的輸入。

這是我的代碼:

<?php 
error_reporting(E_ALL); 

##get find() results and remove DOS carriage returns. 
##The error is thrown on the next line for large files! 
$myData = file_get_contents("tmp11"); 
$newData = str_replace("^M", "", $myData); 

##cleanup Model-Manufacturer field. 
$pattern = '/(Model-Manufacturer:)(\n)(\w+)/i'; 
$replacement = '$1$3'; 
$newData = preg_replace($pattern, $replacement, $newData); 

##cleanup Test_Version field and create comma delimited layout. 
$pattern = '/(Test_Version=)(\d).(\d).(\d)(\n+)/'; 
$replacement = '$1$2.$3.$4  '; 
$newData = preg_replace($pattern, $replacement, $newData); 

##cleanup occasional empty Model-Manufacturer field. 
$pattern = '/(Test_Version=)(\d).(\d).(\d)  (Test_Version=)/'; 
$replacement = '$1$2.$3.$4  Model-Manufacturer:N/A--$5'; 
$newData = preg_replace($pattern, $replacement, $newData); 

##fix occasional Model-Manufacturer being incorrectly wrapped. 
$newData = str_replace("--","\n",$newData); 

##fix 'Binary file' message when find() utility cannot id file. 
$pattern = '/(Binary file).*/'; 
$replacement = ''; 
$newData = preg_replace($pattern, $replacement, $newData); 
$newData = removeEmptyLines($newData); 

##replace colon with equal sign 
$newData = str_replace("Model-Manufacturer:","Model-Manufacturer=",$newData); 

##file stuff 
$fh2 = fopen("tmp2","w"); 
fwrite($fh2, $newData); 
fclose($fh2); 

### Functions. 

##Data cleanup 
function removeEmptyLines($string) 
{ 
     return preg_replace("/(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+/", "\n", $string); 
} 
?> 
+1

您可能可以使用'fread()'以塊的形式獲取它,但是否會對您有所幫助取決於您對其執行的操作類型以及對結果的處理方式。 – 2011-03-09 16:58:29

+0

嘿克里斯。 php.ini文件中有一個用於處理文件/內存大小的屬性。如果我記得,你可以改變數字來增加尺寸。這將/應該允許您處理較大的文件。 – 2011-03-09 16:59:29

+0

@tom史密斯:這不是我的服務器,可悲的是,我的手被綁起來。 – Chris 2011-03-09 17:34:19

回答

66

首先,你應該明白,使用的file_get_contents當你獲取數據的整個串入一個變量,即變量存儲在主機內存中。

如果該字符串大於專用於PHP進程的大小,則PHP將暫停並顯示上面的錯誤消息。

解決方法是將文件作爲指針打開,然後每次取一個塊,這樣如果你有一個500MB的文件,你可以讀取第一個1MB的數據,執行你將要使用它,刪除即從系統內存中取出1MB,並替換爲下一個MB,這使您可以管理將多少數據存儲在內存中。

如果這可以在下面看到一個例子,我將創建就像到node.js的

function file_get_contents_chunked($file,$chunk_size,$callback) 
{ 
    try 
    { 
     $handle = fopen($file, "r"); 
     $i = 0; 
     while (!feof($handle)) 
     { 
      call_user_func_array($callback,array(fread($handle,$chunk_size),&$handle,$i)); 
      $i++; 
     } 

     fclose($handle); 

    } 
    catch(Exception $e) 
    { 
     trigger_error("file_get_contents_chunked::" . $e->getMessage(),E_USER_NOTICE); 
     return false; 
    } 

    return true; 
} 

一個函數,然後用像這樣:

$success = file_get_contents_chunked("my/large/file",4096,function($chunk,&$handle,$iteration){ 
    /* 
     * Do what you will with the {&chunk} here 
     * {$handle} is passed in case you want to seek 
     ** to different parts of the file 
     * {$iteration} is the section fo the file that has been read so 
     * ($i * 4096) is your current offset within the file. 
    */ 

}); 

if(!$success) 
{ 
    //It Failed 
} 

其中一個問題,你會發現你試圖在一個非常大的數據塊上執行幾次regex,不僅如此,而且你的regex是爲匹配整個文件而構建的。

使用以上方法你的正則表達式可能變得毫無用處,因爲你可能只能匹配半組數據,你應該做的是恢復到原始字符串函數如

  • strpos
  • substr
  • trim
  • explode

用於匹配字符串,我在回調中添加了支持,以便處理和當前迭代通過,這將允許您在回調中直接使用該文件,從而允許您使用fseek,ftruncatefwrite等功能。

構建字符串操作的方式效率不高,使用上面提出的方法是一種更好的方法。

希望這會有所幫助。

+9

感謝上帝的理智答案,+1 – Alex 2011-03-09 18:07:10

+1

謝謝,有人不得不這樣做。 – RobertPitt 2011-03-09 18:17:43

+0

非常感謝你這麼詳細的回答!我是一名初學者,像你這樣的答案激勵我更加努力地工作。再次感謝。 – Chris 2011-03-09 19:00:54

1

一個漂亮的醜陋的解決方案取決於文件大小來調整你的內存限制:

$filename = "yourfile.txt"; 
ini_set ('memory_limit', filesize ($filename) + 4000000); 
$contents = file_get_contents ($filename); 

右solutuion將認爲,如果能處理文件以較小的塊,或使用PHP的命令行工具。

如果您的文件是基於行的,您還可以使用fgets逐行處理它。

+0

增加了'fgets'選項。 – vbence 2011-03-09 17:45:00

+8

差勁的答案,如果你對你的應用程序這樣做,那麼你需要回到基礎! – RobertPitt 2011-03-09 18:03:13

+0

@RobertPitt我說這很醜陋,但它是唯一的解決方案。 OP顯然沒有給出任何跡象表明該文件可以用更小的塊處理。而且你故意忽略了以「正確的解決方案」開頭的句子來打擊某人。幹活。 – vbence 2011-03-09 20:33:51

-1

我的建議是使用fread。它可能是一個慢一點,但你沒有使用你所有的記憶... 例如:

//This use filesize($oldFile) memory 
file_put_content($newFile, file_get_content($oldFile)); 
//And this 8192 bytes 
$pNew=fopen($newFile, 'w'); 
$pOld=fopen($oldFile, 'r'); 
while(!feof($pOld)){ 
    fwrite($pNew, fread($pOld, 8192)); 
} 
+0

我的理解是OP不想複製文件,他想用'preg_replace'處理它。 – vbence 2011-03-09 17:14:12

+1

好吧,那麼我猜他仍然可以在fread和fwrite之間執行此操作;) – haltabush 2011-03-09 17:31:06

+0

@vbence&@haltabush:文件上的preg_replace()和str_replace()操作在小文件上可以正常工作。請參閱我的代碼更新後的帖子。 fread()似乎是要走的路。 – Chris 2011-03-09 17:39:34

相關問題