1

2 GB の 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

それは実際には「オープニング」の問題ではなく、処理の問題です

現在のように、解析されたすべての行をメモリに保持する必要はないと確信しています。

データベースや別のファイルなど、解析された行をそれが属する場所に配置しないのはなぜですか?

一度に 1 行だけメモリに保持するコードが作成されます。

于 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いる . これは反復子であるため、各行/行のデータをどう処理するかを決定します。関連する質問を参照してください:キーの列見出しを使用して CSV を配列に処理する

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 の方が良いでしょう。

そうは言っても、スクリプトには 2 つのメモリを消費します。1 つ目は、配列をコピーする foreach です。array_keys に対して foreach を実行し、配列内の文字列エントリを参照して行を取得します。2 番目は、@YourCommonSense によって参照されるものです。ストリーミング モードで動作するようにアルゴリズムを設計する必要があります (つまり、完全なデータセットをメモリに保存する必要はありません)。ざっと見たところ、実現可能に思えます。

于 2012-04-30T19:07:23.820 に答える