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

JD *_*cks 11 php memory-management

我从其他服务器下载CSV文件作为来自供应商的数据馈送.

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

我可以很好地达到那个部分,但我尝试使用爆炸\r\n获得一个行数组,但它失败并出现"内存不足"错误.

echo strlen($contents)和它大约有3050万个字符.

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

Pas*_*TIN 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

我所做的是:

  • 当他们到达时,我会处理数据块(我使用var_dump,但你会使用通常的东西)
  • 请注意,您没有获得"实线":一行的结尾是一个块的开头,同一行的开头是在前一个块的末尾,因此,您必须保留一个块的某些部分之间的电话 stream_write


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

// Register the wrapper
stream_wrapper_register("test", "MyStream")
    or die("Failed to register protocol");
Run Code Online (Sandbox Code Playgroud)


而且,现在,我们执行curl请求,就像写入"真实"文件时一样,就像其他建议的答案一样:

// 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);
Run Code Online (Sandbox Code Playgroud)

请注意,我们不使用真实文件,而是使用伪协议.


这样,每次有一大块数据到达时,MyStream::stream_write方法将被调用,并且能够处理少量数据(当我测试时,我总是得到8192字节,无论我用什么值CURLOPT_BUFFERSIZE)


几点说明:

  • 显然,你需要比我更多地测试它
  • 我的stream_write实现可能不会工作,如果行超过8192字节,你可以修补它;-)
  • 它只是作为一些指针,而不是一个完全工作的解决方案:你必须测试(再次),并可能编码更多!

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

  • 聪明.由于curl 7.9.7`CURLOPT_FILE`被重命名为`CURLOPT_WRITEDATA`,我认为你现在可以使用`CURLOPT_WRITEFUNCTION`做类似的事情,这是一个类似你的`stream_write($ data)`的回调,并节省了对流的需求包装.请参阅http://curl.haxx.se/libcurl/c/curl_easy_setopt.html (6认同)

Ala*_*orm 18

PHP正在窒息,因为它耗尽了内存.不要让curl使用文件内容填充PHP变量,而是使用

CURLOPT_FILE
Run Code Online (Sandbox Code Playgroud)

将文件保存到磁盘的选项.

//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);
Run Code Online (Sandbox Code Playgroud)

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

  • http://stackoverflow.com/a/1342760/4668中的答案比我自己的要好. (6认同)

小智 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.");
?>
Run Code Online (Sandbox Code Playgroud)


Seb*_*olm 5

您可能需要考虑将其保存到临时文件,然后使用fgets或一次读取一行fgetcsv.

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