2017-08-28 11 views
2

私は、1行に値を見つけてCSVファイルの別の列の値を返す方法を探していました。PHPで短時間で大きなCSVファイルを解析する

は、これは私の関数であり、それが正常に動作しますが、小さなファイルで:

function find_user($filename, $id) { 
    $f = fopen($filename, "r"); 
    $result = false; 
    while ($row = fgetcsv($f, 0, ";")) { 
     if ($row[6] == $id) { 
      $result = $row[5]; 
      break; 
     } 
    } 
    fclose($f); 
    return $result; 
} 

問題は、私が仕事しなければならないと、実際のファイルが4ギガバイトの大きさを持っていることです。検索に要する時間は膨大です。

: あり file_get_contents => PHP Fatal error: Allowed memory exhausted

彼らは(私が理解するものから)その私に次の関数を与えるが、私は巨大なCSV値を検索することが容易になります:スタックオーバーフローナビゲート

は、私は次のポストました

function file_get_contents_chunked($file,$chunk_size,$callback) 
{ 
    try 
    { 
     $handle = fopen($file, "r"); 
     $i = 0; 
     while (!feof($handle)) 
     { 
      call_user_func_array($callback,array(fread($handle,$chunk_size),&$handle,$i)); 
      $i++; 
     } 

     fclose($handle); 

    } 
    catch(Exception $e) 
    { 
     trigger_error("file_get_contents_chunked::" . $e->getMessage(),E_USER_NOTICE); 
     return false; 
    } 

    return true; 
} 

と使用の方法は、次のようだ:

$success = file_get_contents_chunked("my/large/file",4096,function($chunk,&$handle,$iteration){ 
    /* 
     * Do what you will with the {&chunk} here 
     * {$handle} is passed in case you want to seek 
     ** to different parts of the file 
     * {$iteration} is the section fo the file that has been read so 
     * ($i * 4096) is your current offset within the file. 
    */ 

}); 

if(!$success) 
{ 
    //It Failed 
} 

問題は、私がいないということです大規模なCSVでの検索をスピードアップするために、私の初期のコードを盛り上がった関数で動作させる方法を知っています。私のPHP知識はそれほど高度ではありません。

+0

ファイルを4096バイトのチャンクで読み取ると、検索するたびにファイル全体を読み取る必要があるため、おそらく速度が向上しません。ファイルをデータベースに一度インポートし、DBの能力を使って素早く自分自身を検索するほうがよいでしょう。 – kmoser

+0

引数2の長さを追加してみましょう。ここで値は行の長さで、それがdiffを作るかどうかを調べます。たとえば '$' = fgetcsv($ f、1024、 ";") '' – crafter

答えて

2

ファイルをどのように読み込んでも、正しい行と列を検索する際に常にすべての文字をスキャンする必要があるため、検索を高速化する方法はありません。最悪のケースは、探している行がファイルの最後の行である場合です。

CSVファイルを適切なインデックスデータベースにインポートし、CSVファイルではなく新しいレコードをそのデータベースにさらに保存するようにアプリケーションを変更する必要があります。

ここには、SQLiteを使用した初歩的な例があります。私は1億レコード(〜5GB)のCSVファイルを作成してテストしました。

はSQLiteのデータベースを作成し、CSVがそれにファイルをインポート:

$f = fopen('db.csv', 'r'); 
$db = new SQLite3('data.db'); 
$db->exec('CREATE TABLE "user" ("id" INT PRIMARY KEY, "name" TEXT, 
    "c1" TEXT, "c2" TEXT, "c3" TEXT, "c4" TEXT, "c5" TEXT)'); 
$stmt = $db->prepare('INSERT INTO "user" 
    ("id", "name", "c1", "c2", "c3", "c4", "c5") VALUES (?, ?, ?, ?, ?, ?, ?)'); 
$stmt->bindParam(1, $id, SQLITE3_INTEGER); 
$stmt->bindParam(2, $name, SQLITE3_TEXT); 
$stmt->bindParam(3, $c1, SQLITE3_TEXT); 
$stmt->bindParam(4, $c2, SQLITE3_TEXT); 
$stmt->bindParam(5, $c3, SQLITE3_TEXT); 
$stmt->bindParam(6, $c4, SQLITE3_TEXT); 
$stmt->bindParam(7, $c5, SQLITE3_TEXT); 
$db->exec('BEGIN TRANSACTION'); 
while ($row = fgetcsv($f, 0, ';')) { 
    list($c1, $c2, $c3, $c4, $c5, $name, $id) = $row; 
    $stmt->execute(); 
} 
$db->exec('COMMIT'); 

これは、6.5ギガバイトのファイルをその結果、私のコンピュータ上で15分かけて、長い時間がかかります。データベースから

検索:

$id = 99999999; 
$db = new SQLite3('data.db'); 
$stmt = $db->prepare('SELECT "name" FROM "user" WHERE "id" = ?'); 
$stmt->bindValue(1, $id, SQLITE3_INTEGER); 
$result = $stmt->execute(); 
print_r($result->fetchArray()); 

これは事実上instantenously実行されます。

関連する問題