2011-03-09 5 views
16

대용량 파일을 다룰 때 경험이 없으므로 어떻게해야할지 잘 모릅니다. 나는 여러 개의 큰 파일을 읽으려고했다. file_get_contents; 작업은 preg_replace()을 사용하여 그들을 청소하고 찌르기입니다.file_get_contents => PHP 치명적 오류 : 사용 가능한 메모리가 소모되었습니다.

내 코드는 작은 파일에서도 정상적으로 실행됩니다. 그러나, 큰 파일 (40메가바이트)는 메모리 소모 오류 트리거 :

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()'를 사용하여 청크로 가져올 수는 있지만 그 작업의 종류와 결과에 따라 달라질 수 있는지 여부가 도움이 될지 여부. –

+0

안녕하세요. php.ini 파일에는 파일/메모리 크기를 처리하는 속성이 있습니다. 기억 나면 크기를 늘리기 위해 번호를 변경할 수 있습니다. 이렇게하면 더 큰 파일을 처리 할 수 ​​있습니다. –

+0

@tom smith : 내 서버가 아니고 슬프게도 내 손이 묶여 있습니다. – Chris

답변

66

당신이 file_get_contents 사용시에 데이터의 전체 문자열을 가져 오는 있다는 것을 이해해야한다 a 변수, 그 변수은 호스트 메모리에 저장됩니다.

문자열이 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 
} 

한 당신 가장 큰 덩어리의 데이터에서 정규 표현식을 여러 번 실행하려고 시도하는 것뿐 아니라 정규 표현식이 전체 파일과 일치하도록 만들어 졌다는 것을 알게 될 것입니다.당신은 단지 데이터의 절반 세트를 일치 될 수 있습니다로 정규식 쓸모가 될 수 위의 방법으로

은, 당신이해야 할 것은 이러한

  • strpos
  • substr
  • 로 기본 문자열 함수로 되돌아이다
  • trim
  • explode

문자열을 일치시키기 위해 핸들과 현재 반복이 전달되도록 콜백에 지원을 추가했습니다.이 경우 콜백 내에서 직접 파일을 사용할 수 있으므로 fseek, ftruncatefwrite과 같은 함수를 사용할 수 있습니다. .

건물의 문자열 조작이 효율적이지 않으므로 위에 제시된 방법을 사용하는 것이 훨씬 더 좋습니다.

희망이 도움이됩니다.

+9

고맙습니다. 고맙습니다. 고맙습니다. +1 – Alex

+1

님, 누군가해야했습니다. – RobertPitt

+0

이렇게 자세한 답변을 부탁드립니다! 나는 초급자이고 너와 같은 대답은 나를 더 열심히하도록 동기를 부여한다. 다시 한번 감사드립니다. – Chris

1

예쁜 추한 솔루션 :

내 코드 것은

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

오른쪽 solutuion는 파일을 처리 할 수있는 경우 생각하는 것 작은 덩어리로 작성하거나 PHP의 명령 행 도구를 사용하십시오.

파일이 회선 기반 인 경우 fgets을 사용하여 줄 단위로 처리 할 수도 있습니다.

+0

'fgets' 옵션이 추가되었습니다. – vbence

+8

답이 좋지 않으면 응용 프로그램에서 이렇게하면 기초로 돌아 가야합니다! – RobertPitt

+0

@RobertPitt 나는 꽤 못생긴다고 말했지만 유일한 해결책이었습니다. OP는 분명히 그 파일이 작은 덩어리로 처리 될 수 있다는 어떠한 징조도주지 않았습니다. 그리고 당신은 고의적으로 누군가를 강타하기위한 "올바른 해결책"으로 시작하는 문장을 무시합니다. 그레트 직업. – vbence

-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

+1

그래, 그는 여전히 fread와 fwrite 사이에서 이것을 할 수 있다고 생각한다.) – haltabush

+0

@vbence & @haltabush : 파일에 대한 preg_replace() 및 str_replace() 작업은 작은 파일에서도 잘 작동한다. 내 코드에 대한 업데이트 된 게시물을 참조하십시오. fread()가가는 길 같습니다. – Chris

관련 문제