【问题标题】:Looping through large file runs out of memory循环遍历大文件内存不足
【发布时间】:2012-11-02 15:03:00
【问题描述】:

[已编辑的 OP 在这里是简短版本]

遍历文件并读取内容,然后写入会导致函数失败。这似乎是一个内存问题。这是我试过的三个版本。

第一次尝试这个:

$file = new SplFileObject($this->getDirectoryPath() . $this->getFileName(), "a+");
$file->setFlags(SplFileObject::DROP_NEW_LINE | SplFileObject::SKIP_EMPTY);

if ($this->exists()) {
    foreach ($file as $line) {
        $tempArray = unserialize($line);
        if ($tempArray['Key'] == $arrayOfData['Key']) {
            foreach ($totalsToBeAdded as $key) {
                $arrayOfData[$key] += $tempArray[$key];
            }
        }
    }
}

$tempString = serialize($arrayOfData);

$file->fwrite("$tempString\r\n");

$this->numLines++;

然后我尝试了这个:

$file = new SplFileObject($this->getDirectoryPath() . $this->getFileName(), "a+");
$file->setFlags(SplFileObject::DROP_NEW_LINE | SplFileObject::SKIP_EMPTY);

if ($this->exists()) {
    while (!$file->eof()) {
        $tempArray = unserialize($file->current());
        if ($tempArray['PartNumber'] == $arrayOfData['PartNumber']) {
            foreach ($totalsToBeAdded as $key) {
                $arrayOfData[$key] += $tempArray[$key];
            }
        }

        $file->next();
    }
}

$tempString = serialize($arrayOfData);

$file->fwrite("$tempString\r\n");

$this->numLines++;

最后我放弃了 SplFileObject 并使用普通的 fopen 等:

$handle = fopen($this->getDirectoryPath() . $this->getFileName(), "a+");

if ($this->exists()) {
    while (false !== ($line = fgets($handle))) {
        $tempArray = unserialize(trim($line));
        if ($tempArray['Key'] == $arrayOfData['Key']) {
            foreach ($totalsToBeAdded as $key) {
                $arrayOfData[$key] += $tempArray[$key];
            }
        }
    }
}

$tempString = serialize($arrayOfData);
fwrite($handle, "$tempString\r\n");
fclose($handle);
$this->numLines++;

编辑以获取更多信息:

我很好奇 PHP 的底层代码在逐行遍历文件时是否使用数组作为迭代器,这可能会杀死它。

而且文件确实开始构建,我可以看到它写入到大约 500-600k 然后它就死了。

最终文件大小约为 10mb。

最后一次更新:

这可行(注意缺少打开和读取文件):

public function writeUnique($arrayOfData, $totalsToBeAdded) {  
        $tempArray = array();

        $handle = fopen($this->fullPath, "a+");

        $tempString = serialize($arrayOfData);
        fwrite($handle, "$tempString\r\n");
        fclose($handle);
        $this->numLines++;
}

虽然这会中断(注意所有正在执行的操作都是循环整个文件然后写入文件):

public function writeUnique($arrayOfData, $totalsToBeAdded) {  
        $tempArray = array();

        $handle = fopen($this->fullPath, "a+");

        if ($this->exists()) {
            while (false !== ($line = fgets($handle))) {

            }
        }

        $tempString = serialize($arrayOfData);
        fwrite($handle, "$tempString\r\n");
        fclose($handle);
        $this->numLines++;
}

更新三号:

我现在已经测试过了:

public function writeUnique($arrayOfData, $totalsToBeAdded) {

    $handle = fopen($this->fullPath, "a+");

    if ($this->exists()) {
        while (false !== ($line = fgets($handle))) {

        }
    }

    $tempString = serialize($arrayOfData);
//        fwrite($handle, "$tempString\r\n"); Commented out the writing.
    fclose($handle);
    $this->numLines++;
}

这行得通。没有失败,内存错误或其他方面。

因此,这似乎要么是重复读取大文件的相同行的问题,要么是函数的写入部分在某种程度上踩到了读取函数的脚趾。老实说,这并没有没道理。我知道每个人都认为这与我的阵列有关。但是我已经完全排除了我所有的逻辑,我只是​​想读/写一个大文件。

【问题讨论】:

  • trime($line) 是一个错字,你的意思是trim 还是你自定义的函数?显然PHP没有trime()函数
  • 您的示例都没有显示 $arrayOfData 或 $totalsToBeAdded 变量的来源。我怀疑当您解析文件时,您会不断添加这些变量并最终耗尽空间?
  • @D-Rock:没有 $arrayOfData 是来自数据库查询的数组。它是 10 列的键 => 值设置。
  • @Anthony:你是对的,这只是一个错字。在源代码中是正确的。

标签: php memory file-io spl


【解决方案1】:

试试:

if ($this->exists()) {
    while (false !== ($line = fgets($handle))) {
        $tempArray = unserialize(trim($line));
        unset($line);
        if ($tempArray['Key'] == $arrayOfData['Key']) {
            foreach ($totalsToBeAdded as $key) {
                $arrayOfData[$key] += $tempArray[$key];
            }
        }
        unset($tempArray);
    }
}

我可以在这里看到的唯一持久数组是 $totalsToBeAdded$arrayOfData,从您的 += 运算符来看,它们看起来是一维的,因此除了微优化之外您无能为力。

【讨论】:

  • 我也这么认为,除了 $totalsToBeAdded 只是硬编码 ' $totalsToBeAdded = array('stuff', 'stuff', etc.);'那里只有 9 件商品。
  • 尝试取消设置命令。没有帮助。不过感谢您的想法。
【解决方案2】:

所以我终于崩溃了,计算了一下我需要 php 在这个文件上完成多少个循环,这个数字是 8,788,338,000,000 次。

这反过来又导致 PHP 超时。为了防止它超时,需要添加这行代码。

set_time_limit(0); // ignore php timeout

现在可以逐行读取和解析临时文件。但是,在大文件(10 mb +)上,完成该功能的时间到目前为止已经超过一个小时(它仍在运行,因为我可以看到临时文件越来越大)。

我得出的结论是,如果速度至关重要,那么将 LARGE 数据集存储到临时 SQL 表中可能会更好。这以前对我来说不是一个选择,但现在我正在用允许它的权力来强迫这个问题。最坏的情况下,这将至少允许它运行。

请注意:这将允许无限循环永远运行并可能会终止服务器。在尝试之前,请确保您知道如何通过 UNIX 终止进程。

【讨论】:

    猜你喜欢
    • 2016-01-01
    • 1970-01-01
    • 2015-08-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-30
    • 2014-04-10
    • 1970-01-01
    相关资源
    最近更新 更多