1

我在打开和读取 2gb csv 文件的内容时遇到问题。每次我运行脚本时,它都会耗尽服务器内存(10GB VPS 云服务器),然后被杀死。我制作了一个测试脚本,想知道是否有人可以查看并确认我在这里没有做任何愚蠢的事情(php 明智),这会导致看起来和异常高的内存使用量。我已经和我的托管公司谈过了,但他们似乎认为这是一个代码问题。所以只是想知道是否有人可以查看这个并确认代码中没有任何内容会导致这种问题。

另外,如果您处理 2GB 的 csv,您以前遇到过这样的事情吗?

谢谢

蒂姆

<?php

ini_set("memory_limit", "10240M");

$start = time();
echo date("Y-m-d H:i:s", $start)."\n";

$file = 'myfile.csv';

$lines = $keys = array();
$line_count = 0;
$csv = fopen($file, "r");

if(!empty($csv))
{
    echo "file open \n";

    while(($csv_line = fgetcsv($csv, null, ',', '"')) !== false)
    {
        if($line_count==0) {
            foreach($csv_line as $item) {
                $keys[] = preg_replace("/[^a-zA-Z0-9]/", "", $item);    
            }
        } else {
            $array = array();
            for ($i = 0; $i <count($csv_line); $i++) {
                $array[$keys[$i]] =  $csv_line[$i]; 
            }
            $lines[] = (object) $array;

            //print_r($array);
            //echo "<br/><br/>";
        }
        $line_count++;
    }

    if ($line_count == 0) {
        echo "invalid csv or wrong delimiter / enclosure ".$file;
    }

} else {
    echo "cannot open ".$file;
}
fclose ($csv);

echo $line_count . " rows \n";

$end = time();
echo date("Y-m-d H:i:s", $end)."\n";

$time = number_format((($end - $start)/60), 2);

echo $time."\n";

echo "peak memory usages ".memory_get_peak_usage(true)."\n";
4

3 回答 3

5

它实际上不是一个“开放”问题,而是处理问题

我相信您不需要像现在那样将所有已解析的行保留在内存中。

为什么不把解析后的行放在它所属的任何地方——数据库或另一个文件或任何东西?

它将使您的代码一次只保留在内存中的一行。

于 2012-04-30T18:52:52.403 回答
2

正如其他人已经指出的那样,您正在将整个 2 GB 文件加载到内存中。您在创建每行包含多个字符串的数组时执行此操作,因此实际上所需的结果内存大于普通文件大小。

您可能希望单独处理 CSV 文件的每一行,最好使用迭代器,例如将每一行作为键控数组返回的迭代器:

$csv = new CSVFile('../data/test.csv');

foreach ($csv as $line) {
    var_dump($line);
}

这里的示例输出:

array(3) {
  ["Make"]=> string(5) "Chevy"
  ["Model"]=> string(4) "1500"
  ["Note"]=> string(6) "loaded"
}
array(3) {
  ["Make"]=> string(5) "Chevy"
  ["Model"]=> string(4) "2500"
  ["Note"]=> string(0) ""
}
array(3) {
  ["Make"]=> string(5) "Chevy"
  ["Model"]=> string(0) ""
  ["Note"]=> string(6) "loaded"
}

这个迭代器的灵感来自于 PHP 中内置的一个叫做SPLFileObject. 由于这是一个迭代器,您可以决定如何处理每一行/行的数据。请参阅相关问题:Process CSV Into Array With Column Headings For Key

class CSVFile extends SplFileObject
{
    private $keys;

    public function __construct($file)
    {
        parent::__construct($file);
        $this->setFlags(SplFileObject::READ_CSV);
    }

    public function rewind()
    {
        parent::rewind();
        $this->keys = parent::current();
        parent::next();
    }

    public function current()
    {
        return array_combine($this->keys, parent::current());
    }

    public function getKeys()
    {
        return $this->keys;
    }
}
于 2012-04-30T19:12:24.190 回答
0

PHP 确实是错误的语言。字符串操作通常会导致在内存中分配字符串的副本,并且垃圾收集只会在脚本结束时发生,当它真的不再需要时。如果你知道怎么做,并且它适合执行环境,那么你最好使用 perl 或 sed/awk。

话虽如此,脚本上有两个内存猪。第一个是 foreach,它复制数组。对 array_keys 执行一次 foreach,然后返回数组中的字符串条目以获取行。第二个是@YourCommonSense 提到的:你应该设计你的算法,使它在流模式下工作(即不需要将完整的数据集存储在内存中)。粗略一看,似乎可行。

于 2012-04-30T19:07:23.820 回答