|
New I/O FileChannel |
||
ファイルに関することならば |
||
Channel インタフェース群をインプリメントしたクラスの 1 つに FileChannel クラスがあります。クラス名に File とつくぐらいですから、ファイルに特化した Channel ということができます。 今回はこの FileChannel に関して取り上げてみましょう。ですが、FileChannel クラスの機能のうち、Channel インタフェース群で定義されているものは Channel 導入編で取り上げましたから、その他の FileChannel クラス特有の機能に関して調べていきましょう。
|
ポジション | |||||||||||||||||
Buffer クラスには、読み書きを行った位置を示す position というプロパティがありましたが、FileChannel クラスにも同様に position があります。とはいうものの、Buffer クラスの他のプロパティ limit と capacity はありません。 ファイルの読み書きを行うと position がどのように変化するか調べてみましょう。
ByteBufferUtility は Buffer のときにも使用しましたが、Buffer の要素表示に使用しています。これ以降のサンプルではほとんどこのクラスを利用しています。 さて、FileChannelTest1 クラスではファイルをリード/ライトでオープンする場合と、アペンドモードでオープンする場合の両方を行っています。 まずはリード/ライトでオープンする場合です。positionCheck1 メソッドがこれに対応します。このメソッドでは RandomAccessFile クラスを使用してファイルをオープンしています。
FileChannel オブジェクトは前回使用したように getChannel メソッドを使用します。もちろん、リードオンリやライトモードでオープンすることも可能です。この場合は FileInputStream と FileOutputStream でファイルをオープンしたときと動作は同じになります。 さて、position をセットしたりゲットするのは position メソッドを使用します。引数なしだとゲット、引数があればセットになります。これも Buffer と同じですね。
Buffer の capacity に相当するものは、ファイルのサイズになります。これは size メソッドで得ることができます。 さて、読み込むファイルを用意しておきましょう。単に数字の 01234... と並んでいるだけのファイルにしておきます。ファイルのサイズは 200 byte です。
ここまでの部分の出力結果は
ファイルのサイズが 0、position の初期値が 0 ということが出力されています。 さて、読み込みをして、さらに position を移動してみます。
ここで行っているのは
実行すると次のようになりました。
数字の 0 は ASCII では 0x30 に相当しますので、正しく読み込めています。10 byte 読み込めば、position も 10 だけ進みます。 position は進めることも、戻すことも可能です。ここでは 5 byte 戻しています。position を移動してからの読み込みを行うと前回の読み込みの出力と半分ダブっているのが分かるので、正しく読み込めています。 最終的には position は 10 byte 読み込んだので 15 になりました。 次は書きこみをしてみましょう。
0x41 は 'A' に相当するので、"ABCD" と書きこみを行っています。
実行した後の sample.txt をエディタなどで開いてみて、15 文字目から "ABCD" とライトされているのを確認してみてください。 次はアペンドモードでファイルをオープンして position がどのように移動するか見てみましょう。アペンドモードでファイルをオープンするには FileOutputStream のコンストラクタで指定します。
同じように初期状態で position の値を出力してみます。結果は次のようになりました。
アペンドモードなので position は 200 になるかと思ったのですが、0 になっていました。 それでは、ここで書きこみを行ったら position が 0 のところに書きこみを行うのでしょうか。さっそく、やってみましょう。
先ほどと同じように、ABCD の文字を書きこんでいます。実行結果は
どうやら、書きこむときにまず position をファイル末に移動してから書きこむようです。結果的に出力されたファイルを見てみても最後に加えられていることが分かります。
|
メモリにマップ | |||||||||||||||
Buffer クラスの派生クラスの 1 つに MappedByteBuffer というのがあります。このクラスだけは Buffer の解説では説明を行いませんでした。というのも、FileChannel クラスと一緒に使わなければ意味がなかったからです。 やっと、ここで登場というわけです。 MappedByteBuffer クラスは、名前が示しているようにマッピングされた Buffer を扱うクラスです。 何にマップされるのかというと、お分かりだと思いますが、メモリです。 Buffer オブジェクトをメモリに直接置くといえば、ByteBuffer の allocateDirect メソッドで生成される Buffer オブジェクトを思い出します (Buffer の解説を参照してください)。これも実際には MappedByteBuffer クラスの派生クラスである DirectByteBuffer クラスのオブジェクトです。 さて、FileChannel クラスと MappedByteBuffer クラスを一緒に使えば、ファイルを Buffer オブジェクトとしてメモリにマップすることができます。 ファイルをメモリにマップするというと分かりにくいので、ファイルのキャッシュをメモリに作るという方が分かりやすいのではないでしょうか。ハードディスクや CD-ROM などはメモリに比べればアクセス速度はかなり遅くなります。そこで、キャッシュを作ることでハードディスクなどにあるファイルでも効率よくアクセスできるようになります。 ファイルをメモリにマップするには FileChannel クラスの map メソッドを使用します。map メソッドの戻り値は MappedByteBuffer オブジェクトになります。また、map メソッドの引数はマッピングを開始する位置とサイズ、第 3 引数はマップの方法を示します。このマップの方法は次の 3 種類があります。
READ_ONLY と READ_WRITE はすぐに分かると思いますが、PRIVATE は分かりにくいですね。ファイルのコピーをマップするので、マップされた MappedByteBuffer オブジェクトに対して書き込みを行っても、もとのファイルには反映されないということになります。 (補) ベータの時にはマップの種類は FileChannel クラスの定数 MAP_RO, MAP_RW, MAP_COW でしたが、FileChannel.MapMode クラスの変数に変更されています。特に MAP_COW が PRIVATE になったので、注意が必要です。 (Apr, 2002) ファイルマッピングのサンプルもファイルのコピーにしてみました。
ファイルのコピーを行うには当然ながら入力のファイルと出力のファイルがありますが、どちらをマップするかで 2 通りの方法があります。FileChannelTest2 では copyUsingMappedByteBuffer1 メソッドが入力元のファイルをマップし、copyUsingMappedByteBuffer2 メソッドが出力先のファイルをマップしています。 FileChannelTest2 の実行には引数としてコピー元ファイル名とコピー先ファイル名が必要になります。実行すると、どちらの方法で行うか聞いてくるので、1 か 2 を入力します (下の例では水色で示した部分が入力を行った部分です)。すると、入力に応じた方法でコピーを行います。
それでは、ソースを見ていきましょう。まずはコピー元ファイルをメモリにマップする copyUsingMappedByteBuffer1 メソッドからです。
map メソッドの引数は、第 1 引数がマップの方法、第 2, 3 引数がマッピングを開始する位置とサイズになります。 コピー元のファイルをマップするので、リードオンリーでマップを行います。この例ではファイルのすべてをマップしていますが、ファイルが大きい場合は何回かに区切って行う必要があると思います。 後は、マップした結果の ByteBuffer オブジェクトをコピー先の Channel オブジェクトに書き出します。 入力の処理がないので変な感じですが、これでコピーができてしまいます。 コピー先のファイルをマップする場合も同じように行います。
ファイルをマップして得られた MappedByteBuffer オブジェクトを使用して、入力を行うようにしています。 赤で書かれた最後の行にある force メソッドはマップされた MappedByteBuffer に加えた変更をファイルに反映させるように行うもので、OutputStream クラスの flush メソッドの MappedByteBuffer クラス版といったところでしょうか。 先ほどの例とは逆に出力の処理がないので変な感じですが、これでコピーはできてしまいます。 次の章では、また違う方法でファイルのコピーを行うサンプルを紹介しましょう。
|
入力と出力をくっつける |
||||||
UNIX を使用しているとパイプがとても便利なことが分かります。もっとも、最近の GNOME や KDE などを使用していると、それほどxterm などを使用しないのでパイプの恩恵にあずかることが少なくなってしまってますが。また、Windows でもいわゆる DOS 窓を通常のオペレーションでは使用しませんから、パイプを使用することもほとんどなく、その便利さが分かっていただけないのが残念です。 簡単にいえばパイプはあるプログラムの標準出力を他のプログラムの標準入力にくっつけるということを行います。こうすることで、複数のプログラムを結びつけることがいとも簡単にできてしまいます。 これと同じようなことを FileChannel クラスでも行うことができます。もっとも、FileChannel クラスなので入力か出力のどちらか一方はファイルになってしまいます。 たとえば、この機能を使用することで通信内容をそのままファイルに保存するようなことがいとも簡単にできてしまいます。 さっそく、この機能を使用したサンプルを作ってみましょう。やはり、ファイルのコピーを題材にしましょう。
FileChannel オブジェクトが入力したもの (要するにファイルの内容) を他の Channel オブジェクトの出力 (実際には WritableByteChannel オブジェクト) にくっつけるには、transferTo メソッドを使用します。 逆に、ReadableByteChannel オブジェクトの出力を FileChannel の入力にする場合は、transferFrom メソッドを使用します。 それぞれ、FileChannelTest3 クラスの copyUsingTransferTo メソッドと copyUsingTransferFrom メソッドが対応しています。copyUsingTransferTo メソッドを次に示します。
赤で示した部分が inputChannel オブジェクトを outputChannel に結び付けている部分です。 transferTo メソッドの引数は第 1 引数が WritableByteChannel オブジェクトに結び付けを開始する位置、第 2 引数がサイズになります。ここでは、ファイルのコピーなのでファイルのはじめから最後まで結び付けを行っています。 transferTo メソッドの最後の第 3 引数が結び付けを行う WritableByteChannel オブジェクトです。FileChannel クラスは WritableByteChannel インタフェースをインプリメントしているので、FileChannel オブジェクトである outputChannel を使用しています。 こうすると、実際に read や write といった入出力の処理をまったく書かなくてもコピーが行えます。なんか、変な感じですね。 次に transferFrom を使用した copyUsingTransferFrom メソッドです。このメソッドは copyUsingTransferTo メソッドとほとんど同じになります。
赤の部分だけが異なるだけで、ここでは transferFrom メソッドを使用しています。transferFrom メソッドを用いることで、他の Channel オブジェクトが入力したものをそのまま FileChannel オブジェクトの出力として使用することができます。 transferFrom メソッドの第 1 引数が FileChannel オブジェクトに結び付けられる ReadableByteChannel オブジェクトです。やはり、FileChannel クラスは ReadableByteChannel インタフェースをインプリメントしていますから、inputChannel がそのまま使用できます。 第 2, 3 引数は transferTo メソッドと同様に結び付けを開始する位置とサイズです。 とてもあっけなくコピーができてしまいました。 FileChannelTest3 クラスでは transferTo/transferFrom で結びつけられる 2 つの Channel オブジェクトは両方とも FileChannel オブジェクトでしたが、SocketChannel オブジェクトなどの他の Channel オブジェクトでもまったくかまいません。それこそ、UNIX のパイプのように何でも結び付けることが可能になるのです (もっとも Channel オブジェクトに限定されますが...)。 ここまで、3 つの手法を用いてファイルのコピーを記述してきました。
単純なコピーの場合、パフォーマンスは transferTo/transferFrom メソッドを使用したものが最も高く、read/write メソッドを最も低いようです。余裕のある方は、Channel の解説で行ったような比較をしてみたらいかがでしょうか。
|
ロック |
|||||||||||||||||||||||||||||||
さて、FileChannel クラスの最後に残された機能はロックです。今までなかったことが不思議なくらいですが、やっとファイルのロックを Java で行うことができるようになりました。 さっそく次のサンプルを実行してみてください。
FileChannelTest4 を実行数には引数にオープンするファイル名を指定します。プログラムの中でファイルのロックを行います。ロックをリリースするか聞いてくるダイアログが出ますので、そのままにして他のプログラムで FIleChannelTest4 でオープンしたファイルをオープンしてみましょう。たとえば、FileChannelTest1 などがいいかもしれません。 なかなか時系列をあらわすのは難しいのですが、こんな感じになります。
ファイルにロックがかかっている状態だと、他のアプリケーションでそのファイルにアクセスを行うとエラーが発生することがお分かりでしょうか。 ファイルをロックしたときに、FileChannelTest1 を実行したものが 4. になり、オープンしただけロックがかかっていない (ロックを解放した) のが 6. になります。4. の場合は例外が発生し、6. では普通に読み書きが行えました。 また、ロックがかかっている状態 (3. の状態) で、例えばメモ帳 (notepad) で sample.txt を開いてみましょう。メモ帳では開くことはできるのですが、上書き保存をしようとすると次のようなダイアログが表示されて、そのままでは保存ができないようになってしまいました。 それでは、プログラムでロックを行うにはどうするのでしょう。FileChannel クラスにはファイルのロックをかけるために 4 つのメソッドが用意されています。lock メソッドと tryLock メソッドで、それぞれ引数なしと引数ありのメソッドがあります。 lock メソッドと tryLock メソッドの違いは、lock メソッドがファイルのロックが得られるまでプログラムの実行がブロックされるのですが、tryLock はロックがとれてもとれなくてもすぐにメソッドから戻ってくるという違いがあります。 ファイルのロックの状態は FIleLock クラスであらわされます。lock メソッドと tryLock メソッドの戻り値は FileLock オブジェクトになります。tryLock メソッドを使用したときは、ロックがとれれば FileLock オブジェクトが戻りますが、とれなければ null が帰ります。 FileChannelTest4 でロックを取得する部分は次のようになります。変数 channel は FileChannel オブジェクトです。
2 行目と 4 行目は単に表示を行っているだけで、実際にロックをかけているのは 3 行目の lock メソッドを使用している部分です。 前述したように lock メソッドはロックがかかるまで処理をブロックします。4 行目の出力結果は次のようになりました。
FileLock クラスにはいくつかプロパティがあります。それらの値が [ と ] に囲まれている部分です。はじめのコロンで区切られている数字はロックしている部分を示しています。はじめの数字がロックの開始位置で、次の数字がサイズです。 次の "exclusive" というのはロックの種類を表しており、"shared" か "exclusive" のどちらかになります。 最後がロックが有効かどうかを表しています。ロックが解放されていれば "invalid" になります。 ここで出力される値は FileLock クラスの position メソッド、size メソッド、isShared メソッド、isValid メソッドで得ることができます。 このように FileLock クラスにはプロパティがあるのですが、引数なしの lock メソッドではこれらを指定することができません。指定するには引数ありの lock メソッドを使用します。
FileChannelTest5 を実行する時には引数として、ファイル名、ロック開始位置、ロックを行うサイズ、シェアードロックかどうかを指定します。 ロックを行っている部分は次のようになっています。
最後から 2 行目で、ロックを行っていますが、FileChannelTest4 とは lock メソッドの引数があるかだけの違いになります。 ファイルの 1 部分だけロックをかけることもできるので、次のようなことも可能です。
このようにロックする位置を変化させれば、複数のアプリケーションでファイルにアクセスすることができました。 また、同じ部分をロックしてもシェアードロックにすれば、読み込みだけは行うことができます。ダイアログの部分などは同じなので省略します。
FileChannelTest5 で sample.txt のすべての部分をロックしても、FileChannelTest1 では読み込みだけは行うことができました。しかし、書き込みは例外が発生しています。
|
最後に |
||
FileChannel クラスにはここで紹介したように、今までにはなかった便利な機能があります。特に筆者にとってうれしいのはロックがやっとサポートされたことです。 今までは、ロックを行うためにいろいろなトリックを使ってきました、例えば、A というファイルをオープンするときには、別に用意した B というファイルにオープンをしたことを書き込みます。そして、他のクラスが A をオープンしようとするときには B を読み込んで、他のクラスで A を読み込んでいないかどうかを確かめてからオープンするようにする、なんてことをやっていました。この場合、自分で作ったアプリケーションであれば、ファイル B を確かめることができるのですが、一般のアプリケーションではそんなことは知らないので、結局ロックがかけられないという問題がありました。 しかし、そんな問題も今は過去のものとなりました。 ただし、Beta 2 でもまだバグは結構あるようです。例えば、Windows 98 で引数なしの lock メソッドが使えませんでした。引数ありの lock メソッドは使用できるのですけど... その他にも、BugParade で検索するとまだまだバグがありそうです。はやく、安定したバージョンで FileChannel が使えるようになるといいですね。 今回使用したサンプルはここからダウンロードできます。 参考 URL
(Oct. 2001)
|
|