Подтвердить что ты не робот

File_get_contents => PHP Неустранимая ошибка: Допустимая память исчерпана

У меня нет опыта работы с большими файлами, поэтому я не уверен, что с этим делать. Я попытался прочитать несколько больших файлов, используя 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);
}
?>
4b9b3361

Ответ 1

Во-первых, вы должны понимать, что при использовании 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 of 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.

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

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

Ответ 2

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

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

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

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

Ответ 3

Мой совет - использовать 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));
}