ファイルサイズを取得する際の注意 FileSize関数

ファイルサイズを取得する際に使用するFileSize関数の注意点を紹介します。

DVDからサイト構築まで

カテゴリー

おすすめサイト

RSS

ファイルサイズを取得する際の注意 FileSize関数

2007/03/10 00:57

ファイルサイズを取得する際に使用するFileSize関数
CGI/Perlを使ってきた人であれば、あまり使用する機会がないかもしれませんが、実は、ファイルを読み込むという作業は、どんな言語を使おうと非常に時間がかかります
どういう事かというと、以下のスクリプトを見てください。

$file = "./data.dat";
if (FALSE == ($fp = @FOpen($file , "r+")){
  print "ファイルのオープンに失敗しました";
  exit(1);
}
FLock($fp);
$array = array();
// ファイル内のデータを1行1行読み込み、配列に格納
for($i = 0; !FEof($fp); $i++){
  $array[$i] = FGets($fp , 8192);
}
FClose($fp);

どこもおかしいとこはないかもしれませんが、では、次の例を見てみてください。

$file = "./data.dat";
if (FALSE == ($fp = @FOpen($file , "r+")){
  print "ファイルのオープンに失敗しました";
  exit(1);
}
FLock($fp);
// ファイル内のデータを1回で全て読み込み・・・
$size = FileSize($file);
$all   = Fread($fp);
FClose($fp);
// 改行で分割する
$array = array();
$array = split("\r?\n" , $all);

取得できるデータはどちらも同じですが、後者の方が圧倒的に速く終わると思います。
というのも、以下のように

$tmp = FGets($fp , 1024);

ファイルからデータを読み込むという作業は非常に時間がかかります。
それを、ForやWhileを使ってファイルの最後まで繰り返し読み込むと、さらに時間がかかります。
つまり、ファイルからデータを読み込むという作業を1回で済ませば、最も速いわけです。
1万行程度のデータファイルからデータを読み込む動作を、この2つの方法で100回ぐらいループさせると、その差が歴然と現れると思います。

FRead関数

FGets関数は、指定されたサイズか、改行が見つかるとそこまでしか読み込んでくれませんが、FRead関数は指定サイズ内に改行があろうと、指定されたサイズまでデータを読み込んでくれます。
なので、FileSize関数でファイルサイズを計測し、Fread関数でファイルの全データを読み込んでしまおうというわけです。
私の場合は、最近はこのパターンで読み込むことが多いです。

ファイルのステイタスがキャッシュされる?

ところで、FileSize関数等で取得するファイルサイズというのは、実は、高速化のためにキャッシュされます。
以下の例を見てください。

$file = "./data.dat";
if (FALSE == ($fp = @FOpen($file , "r+")){
  print "ファイルのオープンに失敗しました";
  exit(1);
}
FLock($fp);
// ファイル内のデータを1回で全て読み込み・・・
$size = FileSize($file);
// ここで一回ファイルサイズを出力
print $size . "\n";
$all   = Fread($fp);
// 新しいデータを加える
$new = "あああああああああああああああ\n";
$all   = $new . $all; 
// ファイルポインタを巻き戻す
ReWind($fp);
// ファイルサイズを0にする
FTruncate($fp , 0);
FPuts($fp , $all);
FClose($fp);
// 保存したあとにもファイルサイズを出力してみる
print FileSize($file);

環境にもよりますが、新しいデータを加えて保存したので、違うサイズが出力されるはずです・・・が。
実は、先ほど言ったとおり、ファイルのステイタス情報というのはキャッシュされるため、なんと同じ値が出力されてしまいます

ClearStatCach関数

この厄介な現象(?)は、ClearStatCash関数を使えば一掃されます。
先ほどの例は、以下のように修正すればきちんと出力されるでしょう。

$file = "./data.dat";
if (FALSE == ($fp = @FOpen($file , "r+")){
  print "ファイルのオープンに失敗しました";
  exit(1);
}
FLock($fp);
// ファイル内のデータを1回で全て読み込み・・・
$size = FileSize($file);
// ここで一回ファイルサイズを出力
print $size . "\n";
$all   = Fread($fp);
// 新しいデータを加える
$new = "あああああああああああああああ\n";
$all   = $new . $all; 
// ファイルポインタを巻き戻す
ReWind($fp);
// ファイルサイズを0にする
FTruncate($fp , 0);
FPuts($fp , $all);
FClose($fp);
// キャッシュをクリア
ClearStatCash();
// これで正常にファイルサイズを出力できる
print FileSize($file);