file_get_contents => Неустранимая ошибка PHP: допустимая память исчерпана - PullRequest
21 голосов
/ 09 марта 2011

У меня нет опыта работы с большими файлами, поэтому я не уверен, что с этим делать.Я попытался прочитать несколько больших файлов, используя file_get_contents ;задача состоит в том, чтобы очистить и удалить их с помощью preg_replace () .

Мой код отлично работает на небольших файлах;однако большие файлы (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);
}
?>

Ответы [ 3 ]

85 голосов
/ 09 марта 2011

Во-первых, вы должны понимать, что при использовании file_get_contents вы извлекаете всю строку данных в переменную , что переменная хранится в памяти хоста.

если эта строка больше размера, выделенного для процесса PHP, тогда PHP остановится и отобразит сообщение об ошибке выше.

Способ обойти это, чтобы открыть файл в качестве указателя, а затем взять порцию за раз. Таким образом, если у вас есть файл размером 500 МБ, вы можете прочитать первые 1 МБ данных, делать то, что вы хотите с ним, удалить это 1 МБ из системной памяти и заменить на следующие МБ. Это позволяет вам управлять объемом данных, помещаемых в память.

В качестве примера, если это можно увидеть ниже, я создам функцию, которая действует как 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, ftruncate и fwrite например.

То, как вы строите свои манипуляции со строками, неэффективно, и использование предложенного выше метода намного лучше.

Надеюсь, это поможет.

4 голосов
/ 09 марта 2011

Довольно уродливое решение для настройки ограничения памяти в зависимости от размера файла:

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

Правильным решением было бы подумать, можете ли вы обрабатывать файл небольшими кусками или использовать инструменты командной строки из PHP.

Если ваш файл основан на строках, вы также можете использовать fgets для его обработки построчно.

0 голосов
/ 09 марта 2011

Мой совет - использовать фред. Это может быть немного медленнее, но вам не придется использовать всю свою память ... Например:

//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));
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...