• ベストアンサー

たくさんのファイルを読み込む処理方法 アドバイスお願いします

php5で数百個のログファイル(情報がカンマで区切られているファイル)に記述されている特定の列の数字を足していきたいのですが、このような場合、どのようにカウントするのが良いかよろしければ皆さんからのアドバイスをお願いします。 ここで、ログファイルは日付による連番であり、たまに過去のファイルも修正される可能性があります。ログファイルの中身はそれぞれ最大10行程度で、それぞれの行の特定の列にある数字を扱う。 例:2つのログファイルがあるとして、それぞれ2行2列ある。 20061126.log 1,1 2,1 20061127.log 1,1 2,0 2つのファイルを読み込み、それぞれのhoge[1][1]を足す。 hoge[1][1]の合計結果は「1」みたいな感じにカウントしたいです・・・。これが数百ファイルになると、何か気をつけなければならない点があるかどうか気がかりです。 ログファイルが作成されたら随時カウントしていく専用の別ファイルを作ろうと思ったのですが、過去のログファイルも更新される可能性があるとなると複雑になってしまうのではないかと思い、他の方法を模索しています。 もしよろしければ具体的なプログラムソースなんかを記述していただければこの上なく嬉しいです。 お手数ですが、せめて、使用すべき関数や気をつける点などがありましたらぜひ教えてください・・。 よろしくお願いいたします!

質問者が選んだベストアンサー

  • ベストアンサー
  • gfct9s
  • ベストアンサー率90% (10/11)
回答No.2

カウント用別ファイルを利用する方式で、ログファイルが更新された時に カウント用ファイルを更新する方式で考えてみました。 確かに複雑になってスマートとはいえず、さらにこの内容でうまく伝わるか という問題もありますが、ご参考になるかもしれませんので回答させていただきます。 ■カウント用ファイルのフォーマット (ファイル名1),(ファイル1の更新時間),(…ファイル名1のデータを横に並べたもの…) (ファイル名2),(ファイル2の更新時間),(…ファイル名2のデータを横に並べたもの…)  : というフォーマットで作成します。 ファイル更新時間の取得は、filemtime()関数を用います。 http://au3.php.net/filemtime 「データを横に並べたもの」は、hoge[X][Y]のデータを下記の式で 横0~nの1次元の並びに変換します。 n = X + Y * Xmax (0 <= X,Y) 質問文の例でいくと、カウント用ファイルの内容は下記になります。 20061026,1164466800,1,1,2,1 20061027,1164553200,1,1,2,0 ■カウント用ファイルの更新 定期的にログファイル全体をチェックし、ログファイルが更新されていないか 調べる処理を行って、カウント用ファイルを更新するとします。 チェック処理の実装は下記のとおりです。 (スペースが全角なので、コピー時はご注意ください) $lastcheck = (どこかに保存した前回チェック時刻。初回時0); $dirpath = '…ログファイルのあるパス…'; $d = dir($dirpath); while ($entry = $d->read()) {   if ($entry == '.' || $entry == '..') continue;   $path = $dirpath . DIRECTORY_SEPARATOR . $entry;   if (is_file($path) && $lastcheck < filemtime($path)) {     // …カウント用ファイルを開いて、     // 対応するログファイルの行を更新、無ければ追加…   } } $d->close(); ■集計値の計算 カウント用ファイルを開き、先頭行から合計していきます。 ■考えられる問題と対策 個別のログファイルが巨大なとき、カウント用ファイルも大きくなって 効率が悪そうです。この場合は、カウント用ファイルを月別に分けるなど して対応します。 カウント用ファイルの更新時に、対象ログファイルの行が見つからずに 追加処理を行う、という処理が繰り返されるとパフォーマンスが悪化します。 この場合は、カウント用ファイル内の行の並びをログファイルの日付順に します。例えば20061101.logは1行目、20061126.logの内容は26行目に記録します。 こうすると、先頭行からログファイル名を照合する必要がありません。

JIITE
質問者

補足

これは良いですね!! まず更新されていないファイルはその中身を見ずに済む点、それとカウントファイルが必要な時だけチェックすればよい点が優れています (自分は、カウントファイルは統計専用のページのみ必要なのに、ログファイルと同時にカウントファイルも更新しようとしていました。うまく伝わるか不安ですが、それだとログファイルが2度更新されたらカウントファイルも2度更新され、2度更新された後に統計ページを見るのであれば1度目のカウントファイル更新は意味の無いものになるので効率が悪い) ちなみに、カウントファイルに必要な情報に絞ればそれぞれのログファイルは150バイト程度ですし、毎日ではなく年に多くても200ファイルと考えれば・・・容量に関してはそこまで心配する必要は無くなりそうです。 一つお聞きしたいのは、 >カウント用ファイル内の行の並びをログファイルの日付順に >します。例えば20061101.logは1行目、20061126.logの内容は26行目に記録します。 >こうすると、先頭行からログファイル名を照合する必要がありません。 カウントファイルの指定した行を書き換えるってのは・・・具体的にどうすれば良いんでしょうか、fgetsでファイルポインタを動かしていく感じで良いでしょうか?もしよろしければ回答お願いします とりあえずどうにかなりそうな気がしてきました。わざわざソースコードまで添えた回答ありがとうございます<(_ _)> 助かってとても感謝しています。

その他の回答 (2)

  • gfct9s
  • ベストアンサー率90% (10/11)
回答No.3

《ANo.2の続きです》 気に入っていただけたようで、安心しました。 カウント用ファイルの更新手法は、ANo.2を書いた時点で3つほど 思いついたんですが、おっしゃるようにfgets()でたどりながらその行だけ 更新するというのは、ファイルサイズが可変長のときは、できそうにないですね。 また何か間違うかもしれませんが、思いつきの2つの方法を紹介します。 ■可変長サイズのカウント用ファイルを用いる場合 その月のカウント用ファイルを初めて作成するときは、まず31行分の 空行だけで作成します。 $statsfile = '…カウント用ファイルのパス…'; if (! file_exists($statsfile)) {   $fp = fopen($statsfile, 'w');   for ($i = 0 ; $i < 31 ; $i++) {     fwrite($fp, "\n");   }   fclose($fp); $fp = null; } 例えば20061126.logの行を更新するときは、file()関数 http://au.php.net/file を用いて一旦全行を配列に読み込み、日付に対応する行を更新します。 $logfilename = '20061126.log'; // ファイル名の日付部分を取得 $day = preg_replace('/^[0-9]{6}([0-9]{2})\\.log$/i', '\\1', $logfilename); // カウント用ファイルを読み込んで、対象行を更新 // (ファイル更新日時を保存していますが、これはたぶん不要です) $lines = file($statsfile); $lines[$day - 1] = $logfilename . ",1164466800,1,1,2,1\n"; // ファイル更新 $fp = fopen($statsfile, 'w'); fwrite($fp, join('', $lines)); fclose($fp); $fp = null; ■固定長サイズのカウント用ファイルを用いる場合 初回のカウント用ファイルを空行31行で作るのは同じですが、 改行を含めて1行が(例えば)256バイトになるように、空白で埋めて作成します。 if (! file_exists($statsfile)) {   $fp = fopen($statsfile, 'w');   for ($i = 0 ; $i < 31 ; $i++) {     fwrite($fp, str_repeat(' ', 255) . "\n"); // ここだけ異なる   }   fclose($fp); $fp = null; } 例えば20061126.logの行を更新するときは、先ほど同様 26行を更新します。日付から書き込み位置を計算して、fseek()関数 http://au.php.net/fseek でファイルポインタを移動します。 $logfilename = '20061126.log'; // ファイル名の日付部分を取得 $day = preg_replace('/^[0-9]{6}([0-9]{2})\\.log$/i', '\\1', $logfilename); // 追加(更新)する行の内容 $newline = $logfilename . ",1164466800,1,1,2,1"; $fp = fopen($statsfile, 'r+'); rewind($fp); fseek($fp, ($day-1)*256); fwrite($fp, $newline . str_repeat(' ', 255 - strlen($newline)) . "\n"); fclose($fp); $fp = null; 1行が256バイトになるように空白埋めをするのは、 初回ファイル作成時と同様です。 もし同一ファイルに複数同時アクセスがありそうでしたら、ANo.1の方がおっしゃるように、 flock()を忘れないようにしてください。 ロックが取得できた場合でも、ほかのプロセスがそのファイルに追記している場合もあり、 ファイルポインタが妙なところを見ている場合があるようです。 この場合は、直前のrewind()が対策になるようです。 http://au.php.net/rewind

JIITE
質問者

お礼

なるほど・・・ $lines = file($statsfile); $lines[$day - 1] で対象行を取得するのですね・・・。とても現実的です。 可変長のカウントファイルから情報を読み取るのは固定長より複雑でしょうが、コチラでがんばってみます。固定長の方法も大変参考になります。 カウントファイルの更新さえできてしまえば、後は自分の力で大丈夫です。 ご助力本当にありがとうございました!

回答No.1

気をつけたほうが良さそうだな、と思った点を以下に書きます。 (性能) 1つのファイルの読込みには、ミリ秒単位の時間がかかると思った方がよいです。数百ファイルだと、結構な時間が掛かります。バッチではなく、オンラインのアプリケーション(ブラウザからのリクエストで起動して、ブラウザに結果を表示するアプリケーション)でやろうとしているのであれば、事前にテストプログラムを書いて、時間測定した方がよいかもしれません(私ならそうします)。 (競合) 書込み同士の競合や、読込みと書込みが競合した場合、ファイルが壊れたり、読み取るデータがおかしくなったりすることがあります。書込み、読込みとも、ファイルオープン時にロックをかけた方が安全です。(ファイルロックにはflock関数を使います)。また、余り心配ないと思いますが、デッドロックを防ぐために、個々のファイルの使用が終わった時点で都度ロック解除(とclose)した方がよいです。 (メモリ) でかいファイルの場合、プログラムがまずいとメモリが不足する可能性があります。このケースでは個々のファイルが小さいようなので、余り心配ないと思いますが。 >もしよろしければ具体的なプログラムソースなんかを記述していただければこの上なく嬉しいです。 プログラムを書ける方だとお見受けしましたので、ソースは省略させてもらいます^^ #そもそもの話として、ファイルではなくデータベースに情報を格納した方がよいのでは、と思います。

JIITE
質問者

お礼

回答ありがとうございました<(_ _)>

JIITE
質問者

補足

やはり気をつけるのはそのくらいでしょうか それとデータベースは利用できないです あとは効率の良いカウント方法を募集します。皆さんならどう処理をするか参考にさせてください。 このくらいのファイル数を扱うのは初めてですので・・経験のある方がいましたらお手数とは思いますがぜひ伝授お願いします。

関連するQ&A