flash.filesystem.FileStreamを用いたファイルの読み書き

ファイルシステムに関する解説の最終回となる今回は、ファイルからのデータ読み取り、ファイルへのデータ書き出しを行う際に使用する、flash.filesystem.FileStreamクラスについて説明する。

FileStreamクラスは、ファイルの読み書きを行うための汎用的なクラスだ。非常に高機能なクラスで、テキスト形式かバイナリ形式かに関わらずファイルをオープンでき、どちらもデータの読み書きを行え、しかも読み書きを同期でも非同期でも行うことができる。更には、ストリーム内の読み込み/書き出しの開始位置(ポジション)を自由に操作して、ファイルへのランダムアクセスを実現することも可能だ。

では、一つ一つ解説していこう。

ファイルの基本的な読み書き

まずは、ファイルの読み書きを同期処理として行う方法を学んでいこう。同期処理はコードが単純になり、理解しやすい一方で、その処理中はユーザインタフェースの更新が行えないという欠点がある。つまり、長時間かかるデータの読み書きを同期的に行ってしまうと、アプリケーションが「固まって」見えてしまうということだ。

まず、ファイルの読み書きを同期で行うための基本的な流れは以下のようなものになる。 ファイルIOのコードとしては非常に一般的な流れなので、理解は容易だろう。

// 読み書き対象となるファイルのインスタンス
var file:File = new File("C:\\Temp\\test.txt");

// FileStreamクラスのインスタンス作成
var stream:FileStream = new FileStream();

// (1) 対象ファイルへのストリームをオープン
stream.open(file, FileMode.READ);

...
// (2) ファイルの読み書きを行う
...

// ストリームをクローズする
stream.close();

(1) ストリームをオープンする際にはFileStream.openメソッドを使用するが、第一引数に読み書き対象のファイル、第二引数にオープンするモードを指定する。オープンするモードはflash.filesystem.FileModeクラス内に定数として定義されている。

  • FileMode.READ - ファイルを読み込み専用で開く
  • FileMode.WRITE - ファイルを書き込み専用で開く。ファイルが存在しない場合は作成される
  • FileMode.APPEND - ファイルを追記専用で開く。ファイルが存在しない場合は作成される
  • FileMode.UPDATE - ファイルを読み書き両用で開く。ファイルが存在しない場合は作成される

(2) ファイルの読み書きは、FileStreamクラスに定義されているreadwriteで始まるメソッドを用いる。まず、一般的なテキストの読み書きを行うには以下のメソッドを用いる

  • readMultiByte(length:uint, charSet:String):String
    lengthに指定したバイト数分ストリームから読み込み、文字列として返す。注意しておきたい点は、マルチバイト文字を分断してしまうようなバイト数をlengthに指定すると、最後の文字が不完全なバイトとして認識されてしまうことだ。そのため、テキストファイルを扱う場合はファイル全体を読み込むようにした方が良い。charSetには、読み込み対象ファイルの文字コードを指定する(サポートされている文字コード)。また、プラットフォームがサポートするデフォルトの文字セットはFile.systemCharsetに定義されているということも知っておくとよいだろう。
  • writeMultiByte(value:String, charSet:String):void
    valueに指定された文字列を、charSetに指定された文字コードでファイルに書き出す。

バイナリの読み書きを行うには、flash.utils.ByteArrayクラス(バイト配列を表すクラス)を用いる以下のメソッドが役に立つ。

  • readBytes(bytes:ByteArray, offset:uint = 0, length:uint = 0):void
    バイナリデータを読み込み、bytesに格納する。offsetにはデータをバイト配列のどこから格納するかを、lengthには読み込む長さを指定する。offsetlengthを省略した場合は、FileStream.bytesAvailableの長さ分読み込む(後述)。
  • writeBytes(bytes:ByteArray, offset:uint = 0, length:uint = 0):void
    指定された配列(bytes)の、どこから(offset)どれだけの(length)データを書き出すかを指定して、ファイルにバイナリデータを書き出す。

以上のメソッドの他にも、ActionScriptのデータ型に応じて読み書きを行うメソッド(readIntreadUTFなど)などが数多く提供されているが、ここでは取り上げない。ちなみに、これらの入出力メソッドはインタフェースflash.utils.IDataInputflash.utils.IDataOutputに定義されたものを実装しており、同様のメソッドはネットワークソケットなどでも使用できる。

さて、上のread~メソッドを使用してファイルを終端まで読み込む方法は以下の二つだ。

  • 読み込むバイト数としてFileStream.bytesAvailable(読み込み可能なバイト数)プロパティの値を指定し、ファイル全体を一気に読み込む
  • flash.errors.EOFErrorを捕捉する

前者のFileStream.bytesAvailableプロパティは、ファイル読み込みを同期で行う場合は、ファイル終端までのバイト数を返す。この値を読み込むバイト数として指定することで、ファイル全体を一気に終端まで読み込むことができる。 後者の方法は、ファイル終端を超えて読み込みを行おうとした場合に発生するエラーを捕捉するというものだ。

筆者としては、EOFErrorの捕捉は必ず行うべきだと考える。そして、テキストを読み込む場合はマルチバイト文字を分断してしまわないように、必ずファイル全体を一度に読み込むようにすべきだ。つまり、以下のようなコードが望ましい。

テキストファイルの読み込みを行うコード例

// 読み込み対象となるファイルのインスタンス
var file:File = new File("C:\\Temp\\test.txt");

// FileStreamクラスのインスタンス作成
var stream:FileStream = new FileStream();
var fileContent:String = "";
try {
    // 対象ファイルへのストリームをオープン
    stream.open(file, FileMode.READ);

    // ファイル全体を文字列として読み込む
    // プラットフォームのデフォルト文字セットを指定
    fileContent = stream.readMultiByte(stream.bytesAvailable, File.systemCharset);
} catch (e:EOFError) {
    // ファイル終端の処理
} catch (e:IOError) {
    // 入出力エラーの処理
} finally {
    // ストリームをクローズする
    stream.close();
}

巨大なバイナリファイルなどに対しては、こうしたファイル全体をメモリに展開するような処理は適していない。そういう場合は、次に説明する非同期のファイル処理を用いる方が良いだろう。

ファイルの読み書きを非同期で行うには

ファイルの読み書きを非同期で行えるのはFileStream APIの大きな利点だ。アプリケーションが「固まった」ように見えないようにするためにも、ある程度大きなファイルの読み書きを行うアプリケーションは、必ず非同期でファイル入出力を行うよう心がけるべきだ。 非同期処理としてファイルの読み書きを行うには、以下のような流れを踏襲する必要がある。

(1) FileStream.addEventListenerメソッドを用いて、ファイルの読み書きが完了した場合や(Event.COMPLETE)読み書きの進捗が変化した場合(ProgressEvent.PROGRESS)に呼び出されるイベントハンドラを必要に応じて登録する。

(2) FileStream.openAsyncメソッドを呼び出す。同期処理のopenメソッドと同じく、読み込み専用や書き込み専用と言ったファイルモードを指定できる。注意すべきは、FileMode.READFileMode.UPDATEを指定した場合は、openAsyncの呼び出し直後からファイルの読み込みが始まるということだ。後述のコード例を見てほしい。

(3) 書き込みの場合は、同期処理の場合と同じようにwrite~メソッドを呼び出して書き込みを行える。書き込み命令は全てキューイングされ、呼び出した順序が変更されることなく書き込みが行われる。

以上を踏まえて、ファイルを非同期に読み込む例を示すので、参考にしていただきたい。

// 読み込み対象となるファイルのインスタンス
var file:File = new File("C:\\Temp\\test.txt");

var reader:FileStream = new FileStream();

// ファイル読み込みの進捗に変更があった際のリスナを登録
reader.addEventListener(ProgressEvent.PROGRESS, function(event:ProgressEvent):void {
    // 進捗率をパーセントで表示
    var percent:uint = uint(event.bytesLoaded / event.bytesTotal * 100);
    trace(percent + "パーセント読み込み完了");
});

// ファイル読み込み完了時のリスナを登録
reader.addEventListener(Event.COMPLETE, function(event:Event):void {
    try {
        var bytes:ByteArray = new ByteArray();
        // ファイル内容全体を読み込み
        reader.readBytes(bytes);

        (略)
    } finally {
        reader.close();
    }
});
// ファイルの非同期読み込み開始
reader.openAsync(file, FileMode.READ);

ファイルへのランダムアクセス

ランダムアクセスとは、ファイル先頭から順次読み書きするのではなく、ファイル内の任意の場所からデータの読み書きを行うことを言う。

FileStreamのAPIを用いれば、ランダムアクセスも非常に簡単だ。「position」プロパティに整数値を指定するだけでファイル内の任意の場所に移動でき、その場所から読み込み/書き込みを行うことができる。

というより、FileStreamを用いた読み書きは常にpositionを起点としたランダムアクセスを行っている、と考えてもよい。つまり、通常のread~write~メソッドを呼び出すと、その読み書きの終了地点までpositionも自動的に変化し、次の読み書きはその場所から行われるというわけだ。その例外はAPPENDモードでファイルをオープンした場合で、このモードは常にファイル末尾への書き込みを行うことしかできないため、positionが指す値は意味を持たない。

以下のコードはUNIXのtailコマンドのように、ファイルの末尾だけを表示するアプリケーションのコードを一部抜き出したものだ。ファイルの末尾から、COUNTが示すバイト数ぶん前の位置をFileStream.positionに設定し、そこからファイル終端まで読み込んでいる(エラー処理やマルチバイト文字の事を考慮していない)。

(ここまでのコードは省略)

// 表示する文字数
const COUNT:uint = 100;

// 読み込み対象のファイル
file = File(event.target);

// FileStreamのインスタンス作成
var reader:FileStream = new FileStream();

// ストリームを同期でオープン
reader.open(file, FileMode.READ);

// COUNTよりも大きなファイルだったら
if (reader.bytesAvailable > COUNT) {
    // ファイルの末尾からCOUNT分前の位置にポジションを移動
    reader.position = reader.bytesAvailable - COUNT;
}
// ファイル末尾まで読み込む
var str:String = reader.readMultiByte(reader.bytesAvailable, "iso-8859-1");
view.text = str; // 文字列を表示

// ストリームのクローズ
reader.close();

(以降のコードは省略)

以上で、ファイルの読み書きを行うためのAPIである、flash.filesystem.FileStreamの説明を終わりとする。単一のクラスでファイルデータの取り扱いに関するあらゆる事ができてしまうので、非常に便利なクラスだということがお分かりいただけたと思う。

次回は、AIRが持つHTMLレンダリング機能について触れていきたい。