【问题标题】:PHP memory exhaused while using array_combine in foreach loop在 foreach 循环中使用 array_combine 时 PHP 内存耗尽
【发布时间】:2016-09-17 10:00:40
【问题描述】:

尝试在foreach 循环中使用array_combine 时遇到问题。最终会报错:

PHP Fatal error:  Allowed memory size of 268435456 bytes exhausted (tried to allocate 85 bytes) in

这是我的代码:

$data = array();
$csvData = $this->getData($file);
if ($columnNames) {
    $columns = array_shift($csvData);
    foreach ($csvData as $keyIndex => $rowData) {
        $data[$keyIndex] = array_combine($columns, array_values($rowData));
    }
}

return $data;

我使用的源文件 CSV 大约有 ~1,000,000 行。这一行

$csvData = $this->getData($file)

我使用 while 循环来读取 CSV 并将其分配到一个数组中,它的工作没有任何问题。麻烦来自array_combineforeach 循环。

您是否有任何想法来解决这个问题或只是有更好的解决方案?

更新

这是读取 CSV 文件的代码(使用 while 循环)

$data = array();
if (!file_exists($file)) {
    throw new Exception('File "' . $file . '" do not exists');
}

$fh = fopen($file, 'r');
while ($rowData = fgetcsv($fh, $this->_lineLength, $this->_delimiter, $this->_enclosure)) {
    $data[] = $rowData;
}
fclose($fh);
return $data;

更新 2

如果您正在处理

【问题讨论】:

  • $this->getData($file) 是否只读取原始文件?
  • @RomanPerekhrest:是的。我将该方法添加到问题中。
  • 您确定错误发生在foreach 循环内而不是$this->getData($file) 操作上?
  • @RomanPerekhrest:我很确定,因为记录的错误表明它来自foreach 循环中的array_combine 方法。而$csvData 携带的数据是正确的。

标签: php arrays csv foreach


【解决方案1】:

实际上,您在内存中保留(或试图保留)整个数据集的两个不同副本。首先,您使用 getData() 将整个 CSV 日期加载到内存中,然后通过循环访问内存中的数据并创建一个新数组,将数据复制到 $data 数组中。

您应该在加载 CSV 数据时使用基于流的读取,以便在内存中只保留一个数据集。如果您使用的是 PHP 5.5+(顺便说一句,您肯定应该这样做),这很简单,只需将您的 getData 方法更改为如下所示:

protected function getData($file) {
    if (!file_exists($file)) {
        throw new Exception('File "' . $file . '" do not exists');
    }

    $fh = fopen($file, 'r');
    while ($rowData = fgetcsv($fh, $this->_lineLength, $this->_delimiter, $this->_enclosure)) {
        yield $rowData;
    }
    fclose($fh);
}

这利用了所谓的generator,这是一个 PHP >= 5.5 的特性。您的代码的其余部分应该继续工作,因为 getData 的内部工作应该对调用代码是透明的(只有一半的事实)。

更新来解释现在如何提取列标题。

$data = array();
$csvData = $this->getData($file);
if ($columnNames) { // don't know what this one does exactly
    $columns = null;
    foreach ($csvData as $keyIndex => $rowData) {
        if ($keyIndex === 0) {
            $columns = $rowData;
        } else {
            $data[$keyIndex/* -1 if you need 0-index */] = array_combine(
                $columns, 
                array_values($rowData)
            );
        }
    }
}

return $data;

【讨论】:

  • 感谢您的回复,但yield 在这个游戏中究竟做了什么?
  • 有一个小问题,我正在使用$columns = array_shift($csvData); 将 CSV 列名移动到数组中,并且 array_combine 将使用该新数组到源数组(来自 csv)。如何强制 yield 返回一个数组而不是一个对象?
  • 你需要做一些不同的事情。 yield 将简单地逐行返回。如果您需要以不同方式处理行0(包含标题的第一行),则需要检查$keyIndexif $keyIndex === 0 然后提取列,if $keyIndex > 0 继续正常处理数据行。
  • 你能用这个更新你的答案吗?我还是很困惑,我第一次听说yieldGenerators :)
猜你喜欢
  • 1970-01-01
  • 2015-05-27
  • 2019-04-25
  • 2014-10-04
  • 2013-07-22
  • 2015-10-01
  • 2015-01-16
  • 2014-12-13
相关资源
最近更新 更多