У меня нет опыта работы с большими файлами, поэтому я не уверен, что с этим делать. Я попытался прочитать несколько больших файлов, используя 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); } ?>
Во-первых, вы должны понимать, что при использовании file_get_contents вы извлекаете всю строку данных в переменную , эта переменная хранится в памяти хостов.
если эта строка больше размера, выделенного для PHP-процесса, тогда PHP остановится и отобразит сообщение об ошибке выше.
Путь вокруг этого, чтобы открыть файл как указатель, а затем взять кусок за раз, таким образом, если у вас есть файл 500 МБ, вы можете прочитать первые 1 МБ данных, сделайте то, что с ним будете, удалите 1 МБ из памяти системы и заменить на следующий 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
для сопоставления строк я добавил поддержку в ftruncate
, чтобы ftruncate
дескриптор и текущую итерацию, это позволит вам работать с файлом непосредственно в вашем ftruncate
, что позволяет использовать, например, ftruncate
функции, как fseek
, ftruncate
и fwrite
.
То, как ваше здание манипулирует строкой, неэффективно, что когда-либо, и используя предлагаемый метод выше, намного лучше.
Надеюсь это поможет.
Довольно уродливое решение для настройки ограничения памяти в зависимости от размера файла:
$filename = "yourfile.txt"; ini_set ('memory_limit', filesize ($filename) + 4000000); $contents = file_get_contents ($filename);
Правильное решение будет состоять в том, чтобы подумать, можете ли вы обработать файл в небольших кусках или использовать инструменты командной строки из PHP.
Если ваш файл является линейным, вы также можете использовать fgets
для его последовательного перевода.
Моим советом было бы использовать 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)); }
в//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)); }