操纵一个长度为3000万个字符的字符串

时间:2009-08-27 17:33:27

标签: php memory-management

我从其他服务器下载CSV文件作为供应商的数据Feed。

我使用curl获取文件的内容并将其保存到名为$contents的变量中。

我可以很好地达到那个部分,但是我尝试通过\r\n进行爆炸以获得一个行数组,但它失败并出现“内存不足”错误。

echo strlen($contents),它大约有3050万个字符。

我需要操纵值并将它们插入到数据库中。我需要做些什么来避免内存分配错误?

7 个答案:

答案 0 :(得分:51)

正如其他答案所说:

  • 你不可能拥有所有内存
  • 解决方案是使用CURLOPT_FILE

但是,你可能不想真正创建一个你想要处理内存中数据的文件......只要它“到达”就使用它。

一种可能的解决方案可能是定义您自己的流包装器,并使用这个,而不是真实的文件,CURLOPT_FILE

首先,请参阅:


现在,让我们举个例子。

首先,让我们创建我们的流包装类:

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,但你会做通常的事情)
  • 请注意,你没有得到“满线”:一行的结尾是一个块的开头,同一行的开头是在前一个块的末尾,所以,你必须保留一些部分调用stream_write
  • 之间的块


接下来,我们注册此流包装器,以与伪协议“test”一起使用:

// 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


几点说明:

  • 你需要比我更多地测试它,显然
  • 如果行长度大于8192字节,我的stream_write实现可能无法正常工作; - )
  • 这只是一些指针,而不是一个完全可行的解决方案:你必须再次测试,并且可能需要更多代码!

不过,我希望这会有所帮助;-)
玩得开心!

答案 1 :(得分:18)

PHP正在窒息,因为它耗尽了内存。不要让curl使用文件内容填充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);

然后,一旦保存文件,而不是使用filefile_get_contents函数(将整个文件加载到内存中,再次杀死PHP),使用fopen和{ {3}}一次读取一行文件。

答案 2 :(得分:12)

Darren Cook对Pascal MARTIN的回应非常有意思。在现代PHP + Curl版本中,可以设置CURLOPT_WRITEFUNCTION选项,以便CURL为每个接收到的“数据块”调用回调。具体来说,“callable”将接收两个参数,第一个参数具有调用curl对象,第二个参数具有数据块。函数应该返回strlen($data)以便卷曲继续发送更多数据。

Callables可以是PHP中的方法。使用这一切,我开发了一个可能的解决方案,我发现前一个解决方案更具可读性(虽然Pascal Martin的响应非常好,但从那以后情况发生了变化)。为简单起见,我使用了公共属性,但我确信读者可以适应和改进代码。甚至可以在达到多行(或字节)时中止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.");
?>

答案 3 :(得分:5)

您可能需要考虑将其保存到临时文件,然后使用fgetsfgetcsv一行读取一行。

这样就可以避免爆炸这么大的字符串所带来的初始大数组。

答案 4 :(得分:3)

  1. memory_limit中增加php.ini
  2. 使用fopen()fgets()
  3. 读取数据

答案 5 :(得分:2)

将其隐藏到文件中。不要试图一次将所有数据保存在内存中。

答案 6 :(得分:0)

NB:

“基本上,如果您使用fopen打开文件,请将其关闭然后取消链接, 它工作正常。但是如果在fopen和fclose之间,你给文件句柄 要cURL做一些写入文件,然后取消链接失败。为什么 这种情况发生在我身边。我认为它可能与Bug#48676“

有关

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

如果您使用的是旧版本的PHP,请务必小心。此页面上有一个简单的修复程序可以双重关闭文件资源:

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