Sopht Square

Tie::FileArray - ドキュメント

名前

Tie::FileArray - ファイルの配列エミュレータ もしくは 永続的配列

概要


    use Tie::FileArray;

    tie @array, 'Tie::FileArray', $filename;

    $line4 = $array[3];
    $array[8] = $line9;
    $last_index = $#array;
    push @array, $new_line;
    # ... 任意の配列操作 ...

    untie @array; # もしくはプログラムの終了
  

説明

目的

このモジュールはファイルを配列を通して間接的に操作します。概要に書かれたものは以下に示すものとほぼ等価です。


    open FILE, "+< $file";
    flock FILE, 2;
    @array = <FILE>;

    # ... 任意の配列操作 ...

    seek FILE, 0, 0;
    print FILE @array;
    truncate FILE, tell(FILE);
    close FILE;
  

しかしこの方法ではファイルを丸ごと配列に読み込むため、ファイルサイズが大きい場合非常にたくさんのメモリを消費してしまいます。ところが配列を用いたファイル操作はプログラマにとっては非常に都合がいいために、たいていの場合コンピュータもしくはプログラマの効率を犠牲にしなければなりません。

Tie::FileArrayモジュールでは、ファイルを配列として処理する利便性を維持しつつ、コンピュータにとっても効率のいい処理を実現します。データベースを用いることにより同様の機能を実現するモジュールもありますが、そのようなものが利用できない環境下においても、このモジュールは純粋な Perlコードなので、Perl5を利用できるすべてのサーバで使用できます。

さらに同種の標準モジュール Tie::File と比べて、利用の方法にもよりますが最低でも3倍、最高では10倍程度高速に動作します。

インデックスファイル

tieの第4引数として、インデックスファイルの扱いを指定できます。指定できるのは"easy", "fast", "safe"の3種類です。デフォルトは"easy"になっています。

easy

easyモードでは、インデックスファイルを作成しません。実行時にデータファイルを走査して、インデックスキャッシュを動的に生成します。この処理はかなり高速に行われるので、あまりにもファイルサイズが大きくない限りこのモードで十分だと思われます(あまり参考になるデータはありませんが、手元の1200行を超えるファイルの走査に0.1秒かからないくらいです)。

fast

fastモードでは、インデックスファイルを前回の実行時に保存しておくことで、データファイルを走査する手間を省き、高速に処理することを可能にしています。インデックスファイルはデフォルトでは拡張子filearrayで保存されます。インデックスファイルはoptimize()が実行されたときにのみ保存されます。これは何らかの書き換えを行った場合には、変数がスコープを抜けるときに(要するにDESTROYが送られるときに)必ず1度実行されます。

safe

safeモードでは、インデックスファイルを生成するのはfastモードと同じですが、書き込み操作があるごとに、インデックスファイルを書き換えていき、常にデータファイルとインデックスファイルの整合性を取ろうとします。easy, fast両モードでは万が一実行中にクラッシュが発生した場合にデータが壊れる可能性が高いですが、safeモードにしておくとデータを復旧できる可能性が高まります。とはいえsafeモードが絶対というわけではないので、こまめなバックアップが重要であるのは変わりありません。その意味ではsafeモードは必要がないとも言えます。動作速度はeasy, fast両モード時に比べてかなり遅くなります(といっても絶望的なほどではないですが。気になるようならベンチマークをしてみてください)。

改行文字

配列の要素は常にファイルの「行」として扱われるのが前提のため、書き出す際に改行文字は入れても入れなくても構いません。以下の2つの処理は同じことです。


    $array[3] = "content of line four";
    $array[3] = "content of line four\n";
  

また、仕様上書き出す文字列に改行コードが含まれているとファイルが破壊されてしまうので、そのような改行文字は強制的に削除します。削除されたくない場合は、あらかじめそれらをエンコードして実行時にデコードをするようにしてください。

効率

ファイルの行ごとのオフセット値をvecを利用してスカラーとしてキャッシュするだけなので、ファイルサイズが大きくなっても消費メモリ量はそれほど変わりません。あなたが扱いたいファイルが 1KBしかなくても、1MBを超えるようなファイルであっても、ほとんど差がなく処理します。これを使えばたとえば掲示板プログラムの場合、メモリを気にして過去ログを生成したりする必要はありません。1つのファイルでシンプルに、一貫したコードを記述することができるようになります。これはプログラムにとっても、プログラマにとっても有益なことではないでしょうか。

高速化

コード中に my $fh = local *FH; のように、ファイルハンドルの型グロブをスカラーに代入している個所がいくつかありますが、Perl 5.6 以降を利用している場合、この部分を my $fh; とすると速くなります。私の利用しているプロバイダの Perl のバージョンに合わせてこれを行わない状態で公開していますが、バージョンが新しい場合は必ずやっておいた方がいいと思います。

バグ

ファイルサイズが大きい場合に以下のような処理は行わないでください。


    foreach (@array) {
	my $line = $_;
	chomp $line;
	# ... 何らかの処理 ...
    }
  

なぜかこのようなコードを実行すると、@arrayの内容すべてを読み込んでからループに入るようです。つまりファイルを丸ごと読み込んだのと同じだけのメモリを消費してしまいます。そしてそれは望まれた処理ではないはずです。代わりに以下のコードを使ってください。


    my $i = 0;
    while (my $line = $array[$i++]) {
	chomp $line;
	# ... 何らかの処理 ...
    }
  

この方法なら1度に読み込まれるのは1行だけなので、メモリをほとんど消費しません。また、メモリ領域の拡張も行われないので、こちらの方がおそらく高速です。

また、モジュールの仕様として、配列内にundefが含まれないことが保証されているので(要素が無くなって初めてundefを返す)、forループを使うよりもこちらの方が高速です。

参考

Tie::File, DB_File

作者

あにゃきち, <anyakichi@sopht.jp>

著作権とライセンス

Copyright © 2003-2004 あにゃきち All rights reserved.

このライブラリはフリーソフトウェアです。Perl と同じ条件で再配布・変更を行って構いません。