Манипулировать строку длиной 30 миллионов символов

Я загружаю CSV-файл с другого сервера в качестве фида данных от поставщика.

Я использую curl для получения содержимого файла и сохранения его в переменной с именем $contents .

Я могу добираться до этой части просто отлично, но я попытался взломать с помощью \r и \n чтобы получить массив строк, но с ошибкой «из памяти».

I echo strlen($contents) и это около 30,5 миллионов символов.

Мне нужно манипулировать значениями и вставлять их в базу данных. Что мне нужно сделать, чтобы избежать ошибок выделения памяти?

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

 CURLOPT_FILE 

вместо этого сохраните файл на диске.

 //pseudo, untested code to give you the idea $fp = fopen('path/to/save/file', 'w'); curl_setopt($ch, CURLOPT_FILE, $fp); curl_exec ($ch); curl_close ($ch); fclose($fp); 

Затем, как только файл будет сохранен, вместо использования функций file или file_get_contents (которые будут загружать весь файл в память, снова убивая PHP), используйте fopen и fgets для чтения файла по одной строке за раз.

Как говорили другие ответы:

  • вы не можете иметь все это в памяти
  • решением было бы использовать CURLOPT_FILE

Но вы, возможно, не захотите действительно создать файл; вы можете захотеть работать с данными в памяти … Используя его, как только он «придет».

Одним из возможных решений может стать определение собственной потоковой оболочки и использование этого вместо реального файла с CURLOPT_FILE

Прежде всего, см.

  • stream_wrapper_register
  • The streamWrapper class
  • Пример класса, зарегистрированного как обтекатель потоков

А теперь давайте рассмотрим пример.

Сначала давайте создадим класс оболочки потока:

 class MyStream { protected $buffer; function stream_open($path, $mode, $options, &$opened_path) { // Has to be declared, it seems... return true; } public function stream_write($data) { // Extract the lines ; on y tests, data was 8192 bytes long ; never more $lines = explode("\n", $data); // The buffer contains the end of the last line from previous time // => Is goes at the beginning of the first line we are getting this time $lines[0] = $this->buffer . $lines[0]; // And the last line os only partial // => save it for next time, and remove it from the list this time $nb_lines = count($lines); $this->buffer = $lines[$nb_lines-1]; unset($lines[$nb_lines-1]); // Here, do your work with the lines you have in the buffer var_dump($lines); echo '<hr />'; return strlen($data); } } не class MyStream { protected $buffer; function stream_open($path, $mode, $options, &$opened_path) { // Has to be declared, it seems... return true; } public function stream_write($data) { // Extract the lines ; on y tests, data was 8192 bytes long ; never more $lines = explode("\n", $data); // The buffer contains the end of the last line from previous time // => Is goes at the beginning of the first line we are getting this time $lines[0] = $this->buffer . $lines[0]; // And the last line os only partial // => save it for next time, and remove it from the list this time $nb_lines = count($lines); $this->buffer = $lines[$nb_lines-1]; unset($lines[$nb_lines-1]); // Here, do your work with the lines you have in the buffer var_dump($lines); echo '<hr />'; return strlen($data); } } 

Что я делаю:

  • работайте над кусками данных (я использую var_dump, но вместо этого вы делаете свои обычные вещи), когда они прибудут
  • Обратите внимание, что вы не получаете «полные строки»: конец строки – это начало фрагмента, а начало этой же строки было в конце предыдущего фрагмента; поэтому, вы должны держать некоторые части chunck между вызовами stream_write

Затем мы регистрируем эту обертку потока, которая будет использоваться с псевдопротокольным «тестом»:

 // Register the wrapper stream_wrapper_register("test", "MyStream") or die("Failed to register protocol"); 

И теперь, мы делаем запрос на завивание, как мы это делали бы при написании «реального» файла, как и другие ответы:

 // Open the "file" $fp = fopen("test://MyTestVariableInMemory", "r+"); // Configuration of curl $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, "http://www.rue89.com/"); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_BUFFERSIZE, 256); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_FILE, $fp); // Data will be sent to our stream ;-) curl_exec($ch); curl_close($ch); // Don't forget to close the "file" / stream fclose($fp); 

Обратите внимание, что мы не работаем с реальным файлом, а с нашим псевдопротоком.

Таким образом, каждый раз, когда кусок данных поступает, MyStream::stream_write будет вызван и сможет работать с небольшим количеством данных (когда я тестировал, я всегда получал 8192 байта, какое бы значение я использовал для CURLOPT_BUFFERSIZE )

Несколько примечаний:

  • Тебе нужно проверить это больше, чем я, очевидно
  • моя реализация stream_write, вероятно, не будет работать, если строки длиннее 8192 байта; до вас исправлять его 😉
  • Это всего лишь несколько указателей, а не полностью работающее решение: вам нужно проверить (снова) и, вероятно, немного больше кода!

Тем не менее, я надеюсь, что это поможет 😉
Повеселись !

Комментарий Даррена Кука к ответу Паскаля МАРТИНА действительно интересен. В современных версиях PHP + Curl опция CURLOPT_WRITEFUNCTION может быть установлена ​​так, чтобы CURLOPT_WRITEFUNCTION обратный вызов для каждого полученного «куска» данных. В частности, «вызываемый» получит два параметра, первый из которых содержит вызывающий объект curl, а второй – блок данных. Функция должна возвращать strlen($data) , чтобы завивать, продолжая отправлять больше данных.

Callables могут быть методами в PHP. Используя все это, я разработал возможное решение, которое я считаю более читаемым, чем предыдущий (хотя ответ Паскаля Мартина действительно велик, с тех пор все изменилось). Я использовал публичные атрибуты для простоты, но я уверен, что читатели смогут адаптировать и улучшить код. Вы можете даже прервать запрос CURL, когда достигнут ряд строк (или байтов). Надеюсь, это будет полезно для других.

 <? class SplitCurlByLines { public function curlCallback($curl, $data) { $this->currentLine .= $data; $lines = explode("\n", $this->currentLine); // The last line could be unfinished. We should not // proccess it yet. $numLines = count($lines) - 1; $this->currentLine = $lines[$numLines]; // Save for the next callback. for ($i = 0; $i < $numLines; ++$i) { $this->processLine($lines[$i]); // Do whatever you want ++$this->totalLineCount; // Statistics. $this->totalLength += strlen($lines[$i]) + 1; } return strlen($data); // Ask curl for more data (!= value will stop). } public function processLine($str) { // Do what ever you want (split CSV, ...). echo $str . "\n"; } public $currentLine = ''; public $totalLineCount = 0; public $totalLength = 0; } // SplitCurlByLines // Just for testing, I will echo the content of Stackoverflow // main page. To avoid artifacts, I will inform the browser about // plain text MIME type, so the source code should be vissible. Header('Content-type: text/plain'); $splitter = new SplitCurlByLines(); // Configuration of curl $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, "http://stackoverflow.com/"); curl_setopt($ch, CURLOPT_WRITEFUNCTION, array($splitter, 'curlCallback')); curl_exec($ch); // Process the last line. $splitter->processLine($splitter->currentLine); curl_close($ch); error_log($splitter->totalLineCount . " lines; " . $splitter->totalLength . " bytes."); ?> 

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

Таким образом, вы избегаете первоначального большого массива, который вы получаете от взрыва такой большой строки.

  1. Увеличьте memory_limit в php.ini .
  2. Прочитайте данные с помощью fopen() и fgets() .

Заправьте его в файл. Не пытайтесь хранить все эти данные в памяти сразу.

NB:

«В принципе, если вы открываете файл с fopen, fclose, а затем отключаете его, он работает нормально. Но если между fopen и fclose вы передаете дескриптор файла cURL, чтобы сделать запись в файл, тогда сбой невозможен. это происходит вне меня. Я думаю, это может быть связано с Bug # 48676 "

http://bugs.php.net/bug.php?id=49517

Поэтому будьте осторожны, если вы используете более старую версию PHP. На этой странице есть простое исправление для двойного закрытия файлового ресурса:

 fclose($fp); if (is_resource($fp)) fclose($fp);