Go to Previous Page Go to Contents Go to Java Page Go to Next Page
New Features of Java2 SDK, Standard Edition, v1.4
 
 

New I/O FileChannel

 
 

ファイルに関することならば

 
 

Channel インタフェース群をインプリメントしたクラスの 1 つに FileChannel クラスがあります。クラス名に File とつくぐらいですから、ファイルに特化した Channel ということができます。

今回はこの FileChannel に関して取り上げてみましょう。ですが、FileChannel クラスの機能のうち、Channel インタフェース群で定義されているものは Channel 導入編で取り上げましたから、その他の FileChannel クラス特有の機能に関して調べていきましょう。

 

 
  ポジション  
 

Buffer クラスには、読み書きを行った位置を示す position というプロパティがありましたが、FileChannel クラスにも同様に position があります。とはいうものの、Buffer クラスの他のプロパティ limit と capacity はありません。

ファイルの読み書きを行うと position がどのように変化するか調べてみましょう。

アプリケーションのソース FileChannelTest1.java
ByteBufferUtility

ByteBufferUtility は Buffer のときにも使用しましたが、Buffer の要素表示に使用しています。これ以降のサンプルではほとんどこのクラスを利用しています。

さて、FileChannelTest1 クラスではファイルをリード/ライトでオープンする場合と、アペンドモードでオープンする場合の両方を行っています。

まずはリード/ライトでオープンする場合です。positionCheck1 メソッドがこれに対応します。このメソッドでは RandomAccessFile クラスを使用してファイルをオープンしています。

    public void positionCheck1(String filename){
        try{
            RandomAccessFile raFile = new RandomAccessFile(filename, "rw");
            FileChannel channel = raFile.getChannel();
 
            System.out.println("\n\nFile Opened as Read Write mode.");

FileChannel オブジェクトは前回使用したように getChannel メソッドを使用します。もちろん、リードオンリやライトモードでオープンすることも可能です。この場合は FileInputStream と FileOutputStream でファイルをオープンしたときと動作は同じになります。

さて、position をセットしたりゲットするのは position メソッドを使用します。引数なしだとゲット、引数があればセットになります。これも Buffer と同じですね。

            System.out.println("\nSize: " + channel.size());
            System.out.println("Position: " + channel.position());

Buffer の capacity に相当するものは、ファイルのサイズになります。これは size メソッドで得ることができます。

さて、読み込むファイルを用意しておきましょう。単に数字の 01234... と並んでいるだけのファイルにしておきます。ファイルのサイズは 200 byte です。

サンプルファイル sample.txt

ここまでの部分の出力結果は

C:\temp>java FileChannelTest1 sample.txt


File Opened as Read Only mode.

Size: 200
Position: 0

ファイルのサイズが 0、position の初期値が 0 ということが出力されています。

さて、読み込みをして、さらに position を移動してみます。

            ByteBuffer buffer = ByteBuffer.allocate(10);
            channel.read(buffer);
 
            System.out.println("Read 10 bytes");
            ByteBufferUtility.printByteBuffer(buffer);
 
            System.out.println("Position: " + channel.position());
 
            System.out.print("\nPosition changes from " + channel.position());
 
            channel.position(5);
 
            System.out.println(" to " + channel.position());
 
            System.out.println("\nRead 10 bytes");
            buffer.clear();
            channel.read(buffer);
 
            ByteBufferUtility.printByteBuffer(buffer);
            System.out.println("Position: " + channel.position());

ここで行っているのは

  1. 10 byte 読み込み
  2. position を 5 に移動
  3. 再び 10 byte 読み込み

実行すると次のようになりました。

Read 10 bytes
                                        PLC
 [30, 31, 32, 33, 34, 35, 36, 37, 38, 39]
Position: 10

Position changes from 10 to 5

Read 10 bytes
                                        PLC
 [35, 36, 37, 38, 39, 30, 31, 32, 33, 34]
Position: 15

数字の 0 は ASCII では 0x30 に相当しますので、正しく読み込めています。10 byte 読み込めば、position も 10 だけ進みます。

position は進めることも、戻すことも可能です。ここでは 5 byte 戻しています。position を移動してからの読み込みを行うと前回の読み込みの出力と半分ダブっているのが分かるので、正しく読み込めています。

最終的には position は 10 byte 読み込んだので 15 になりました。

次は書きこみをしてみましょう。

            System.out.println("\nWrite 4 bytes");
            buffer = ByteBuffer.wrap(new byte[]{0x41, 0x42, 0x43, 0x44});
            channel.write(buffer);
 
            System.out.println("Position: " + channel.position());

0x41 は 'A' に相当するので、"ABCD" と書きこみを行っています。

Write 4 bytes
Position: 19

実行した後の sample.txt をエディタなどで開いてみて、15 文字目から "ABCD" とライトされているのを確認してみてください。

次はアペンドモードでファイルをオープンして position がどのように移動するか見てみましょう。アペンドモードでファイルをオープンするには FileOutputStream のコンストラクタで指定します。

    public void positionCheck2(String filename){
        try{
            FileOutputStream stream = new FileOutputStream(filename, true);
            FileChannel channel = stream.getChannel();
 
            System.out.println("\n\nFile Opened as Append mode.");
 
            System.out.println("\nSize: " + channel.size());
            System.out.println("Position: " + channel.position());

同じように初期状態で position の値を出力してみます。結果は次のようになりました。

File Opened as Append mode.

Size: 200
Position: 0

アペンドモードなので position は 200 になるかと思ったのですが、0 になっていました。

それでは、ここで書きこみを行ったら position が 0 のところに書きこみを行うのでしょうか。さっそく、やってみましょう。

            ByteBuffer buffer = ByteBuffer.wrap(
                             new byte[]{0x41, 0x42, 0x43, 0x44});
            channel.write(buffer);

            System.out.println("\nWrite 4bytes");

            System.out.println("\nSize: " + channel.size());
            System.out.println("Position: " + channel.position());

先ほどと同じように、ABCD の文字を書きこんでいます。実行結果は

Write 4bytes

Size: 204
Position: 204

どうやら、書きこむときにまず 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 種類があります。

定数 説明
FileChannel.MapMode.READ_ONLY リードオンリのマッピング
FileChannel.MapMode.READ_WRITE リード/ライトが可能なマッピング
FileChannel.MapMode.PRIVATE ファイルのコピーのマッピング (Copy on Write)

READ_ONLY と READ_WRITE はすぐに分かると思いますが、PRIVATE は分かりにくいですね。ファイルのコピーをマップするので、マップされた MappedByteBuffer オブジェクトに対して書き込みを行っても、もとのファイルには反映されないということになります。

(補) ベータの時にはマップの種類は FileChannel クラスの定数 MAP_RO, MAP_RW, MAP_COW でしたが、FileChannel.MapMode クラスの変数に変更されています。特に MAP_COW が PRIVATE になったので、注意が必要です。 (Apr, 2002)

ファイルマッピングのサンプルもファイルのコピーにしてみました。

アプリケーションのソース FileChannelTest2.java

ファイルのコピーを行うには当然ながら入力のファイルと出力のファイルがありますが、どちらをマップするかで 2 通りの方法があります。FileChannelTest2 では copyUsingMappedByteBuffer1 メソッドが入力元のファイルをマップし、copyUsingMappedByteBuffer2 メソッドが出力先のファイルをマップしています。

FileChannelTest2 の実行には引数としてコピー元ファイル名とコピー先ファイル名が必要になります。実行すると、どちらの方法で行うか聞いてくるので、1 か 2 を入力します (下の例では水色で示した部分が入力を行った部分です)。すると、入力に応じた方法でコピーを行います。

C:\temp>java FileChannelTest2 sample.txt sample1.txt
どちらの方法でコピーしますか
1: コピー元をメモリにマップ 2: コピー先をメモリにマップ
> 1
コピー元ファイルをメモリにマップしてコピーを行います
C:\temp>

それでは、ソースを見ていきましょう。まずはコピー元ファイルをメモリにマップする copyUsingMappedByteBuffer1 メソッドからです。

    private void copyUsingMappedByteBuffer1(String srcFilename,
                                            String destFilename){
        System.out.println("コピー元ファイルをメモリにマップしてコピーを行います");
 
        try{
            FileInputStream in = new FileInputStream(srcFilename);
            FileChannel inputChannel = in.getChannel();
 
            FileOutputStream out = new FileOutputStream(destFilename);
            FileChannel outputChannel = out.getChannel();
 
            ByteBuffer buffer = inputChannel.map(FileChannel.MapMode.READ_ONLY,
                                                 0, (int)inputChannel.size());
            outputChannel.write(buffer);
 
            outputChannel.close();
            inputChannel.close();
 
        }catch(FileNotFoundException ex){
            ex.printStackTrace();
        }catch(IOException ex){
            ex.printStackTrace();
        }
    }

map メソッドの引数は、第 1 引数がマップの方法、第 2, 3 引数がマッピングを開始する位置とサイズになります。

コピー元のファイルをマップするので、リードオンリーでマップを行います。この例ではファイルのすべてをマップしていますが、ファイルが大きい場合は何回かに区切って行う必要があると思います。

後は、マップした結果の ByteBuffer オブジェクトをコピー先の Channel オブジェクトに書き出します。

入力の処理がないので変な感じですが、これでコピーができてしまいます。

コピー先のファイルをマップする場合も同じように行います。

    private void copyUsingMappedByteBuffer2(String srcFilename,
                                            String destFilename){
        System.out.println("コピー先ファイルをメモリにマップしてコピーを行います");
 
        try{
            FileInputStream in = new FileInputStream(srcFilename);
            FileChannel inputChannel = in.getChannel();
  
            RandomAccessFile out = new RandomAccessFile(destFilename, "rw");
            FileChannel outputChannel = out.getChannel();
 
            MappeddByteBuffer buffer = outputChannel.map(FileChannel.MapMode.READ_WRITE,
                                               0, (int)inputChannel.size());
            inputChannel.read(buffer);
            buffer.force();
 
            outputChannel.close();
            inputChannel.close();
 
        }catch(FileNotFoundException ex){
            ex.printStackTrace();
        }catch(IOException ex){
            ex.printStackTrace();
        }
    }

ファイルをマップして得られた MappedByteBuffer オブジェクトを使用して、入力を行うようにしています。

赤で書かれた最後の行にある force メソッドはマップされた MappedByteBuffer に加えた変更をファイルに反映させるように行うもので、OutputStream クラスの flush メソッドの MappedByteBuffer クラス版といったところでしょうか。

先ほどの例とは逆に出力の処理がないので変な感じですが、これでコピーはできてしまいます。

次の章では、また違う方法でファイルのコピーを行うサンプルを紹介しましょう。

 

 
 

入力と出力をくっつける

 
 

UNIX を使用しているとパイプがとても便利なことが分かります。もっとも、最近の GNOME や KDE などを使用していると、それほどxterm などを使用しないのでパイプの恩恵にあずかることが少なくなってしまってますが。また、Windows でもいわゆる DOS 窓を通常のオペレーションでは使用しませんから、パイプを使用することもほとんどなく、その便利さが分かっていただけないのが残念です。

簡単にいえばパイプはあるプログラムの標準出力を他のプログラムの標準入力にくっつけるということを行います。こうすることで、複数のプログラムを結びつけることがいとも簡単にできてしまいます。

これと同じようなことを FileChannel クラスでも行うことができます。もっとも、FileChannel クラスなので入力か出力のどちらか一方はファイルになってしまいます。

たとえば、この機能を使用することで通信内容をそのままファイルに保存するようなことがいとも簡単にできてしまいます。

さっそく、この機能を使用したサンプルを作ってみましょう。やはり、ファイルのコピーを題材にしましょう。

アプリケーションのソース FileChannelTest3.java

FileChannel オブジェクトが入力したもの (要するにファイルの内容) を他の Channel オブジェクトの出力 (実際には WritableByteChannel オブジェクト) にくっつけるには、transferTo メソッドを使用します。

逆に、ReadableByteChannel オブジェクトの出力を FileChannel の入力にする場合は、transferFrom メソッドを使用します。

それぞれ、FileChannelTest3 クラスの copyUsingTransferTo メソッドと copyUsingTransferFrom メソッドが対応しています。copyUsingTransferTo メソッドを次に示します。

    private void copyUsingTransferTo(String srcFilename, String destFilename){
        System.out.println("TransferTo を使用してコピーを行います");
 
        try{
            FileInputStream in = new FileInputStream(srcFilename);
            FileChannel inputChannel = in.getChannel();
 
            FileOutputStream out = new FileOutputStream(destFilename);
            FileChannel outputChannel = out.getChannel();
 
            inputChannel.transferTo(0, (int)inputChannel.size(), outputChannel);
 
            outputChannel.close();
            inputChannel.close();
 
        }catch(FileNotFoundException ex){
            ex.printStackTrace();
        }catch(IOException ex){
            ex.printStackTrace();
        }
    }

赤で示した部分が inputChannel オブジェクトを outputChannel に結び付けている部分です。

transferTo メソッドの引数は第 1 引数が WritableByteChannel オブジェクトに結び付けを開始する位置、第 2 引数がサイズになります。ここでは、ファイルのコピーなのでファイルのはじめから最後まで結び付けを行っています。

transferTo メソッドの最後の第 3 引数が結び付けを行う WritableByteChannel オブジェクトです。FileChannel クラスは WritableByteChannel インタフェースをインプリメントしているので、FileChannel オブジェクトである outputChannel を使用しています。

こうすると、実際に read や write といった入出力の処理をまったく書かなくてもコピーが行えます。なんか、変な感じですね。

次に transferFrom を使用した copyUsingTransferFrom メソッドです。このメソッドは copyUsingTransferTo メソッドとほとんど同じになります。

    private void copyUsingTransferFrom(String srcFilename, String destFilename){
        System.out.println("TransferTo を使用してコピーを行います");

        try{
            FileInputStream in = new FileInputStream(srcFilename);
            FileChannel inputChannel = in.getChannel();

            FileOutputStream out = new FileOutputStream(destFilename);
            FileChannel outputChannel = out.getChannel();

            outputChannel.transferFrom(inputChannel, 0, (int)inputChannel.size());

            outputChannel.close();
            inputChannel.close();

        }catch(FileNotFoundException ex){
            ex.printStackTrace();
        }catch(IOException ex){
            ex.printStackTrace();
        }
    }

赤の部分だけが異なるだけで、ここでは 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 つの手法を用いてファイルのコピーを記述してきました。

  1. read/write メソッドを用いた、最も普通のコピー
  2. map メソッドを使用して、ファイルをメモリにマップする方法
  3. transferTo/transferFrom メソッドを使用して、2 つの Channel オブジェクトを結びつける方法

単純なコピーの場合、パフォーマンスは transferTo/transferFrom メソッドを使用したものが最も高く、read/write メソッドを最も低いようです。余裕のある方は、Channel の解説で行ったような比較をしてみたらいかがでしょうか。

 

 
 

ロック

 
 

さて、FileChannel クラスの最後に残された機能はロックです。今までなかったことが不思議なくらいですが、やっとファイルのロックを Java で行うことができるようになりました。

さっそく次のサンプルを実行してみてください。

アプリケーションのソース FileChannelTest4.java

FileChannelTest4 を実行数には引数にオープンするファイル名を指定します。プログラムの中でファイルのロックを行います。ロックをリリースするか聞いてくるダイアログが出ますので、そのままにして他のプログラムで FIleChannelTest4 でオープンしたファイルをオープンしてみましょう。たとえば、FileChannelTest1 などがいいかもしれません。

なかなか時系列をあらわすのは難しいのですが、こんな感じになります。

1. FileChannelTest4 の実行

C:\temp>java FileChannelTest4 sample.txt
Acquire Lock.
Lock: sun.nio.ch.FileLockImpl[0:9223372036854775807 exclusive valid]
Read 10 bytes
                                        PLC
 [30, 31, 32, 33, 34, 35, 36, 37, 38, 39]

2. ファイルのロックを解放するかを問い合わせるダイアログが表示される

ロックをリリースするダイアログ

3. ダイアログはそのままにしておく

4. 他の DOS プロンプトで FileChannelTest1 の実行

C:\temp>java FileChannelTest1 sample.txt
 
File Opened as Read Write mode.
 
Size: 200
Position: 0
java.io.IOException: プロセスはファイルにアクセスできません。別のプロセスがファ
イルの一部をロックしています。
        at sun.nio.ch.FileDispatcher.read0(Native Method)
        at sun.nio.ch.FileDispatcher.read(FileDispatcher.java:32)
        at sun.nio.ch.IOUtil.readIntoNativeBuffer(IOUtil.java:212)
        at sun.nio.ch.IOUtil.read(IOUtil.java:195)
        at sun.nio.ch.FileChannelImpl.read(FileChannelImpl.java:112)
        at FileChannelTest1.positionCheck1(FileChannelTest1.java:23)
        at FileChannelTest1.<init>(FileChannelTest1.java:8)
        at FileChannelTest1.main(FileChannelTest1.java:86)
  
  
File Opened as Append mode.
 
Size: 200
Position: 0
java.io.IOException: プロセスはファイルにアクセスできません。別のプロセスがファ
イルの一部をロックしています。
        at sun.nio.ch.FileDispatcher.write0(Native Method)
        at sun.nio.ch.FileDispatcher.write(FileDispatcher.java:48)
        at sun.nio.ch.IOUtil.writeFromNativeBuffer(IOUtil.java:108)
        at sun.nio.ch.IOUtil.write(IOUtil.java:90)
        at sun.nio.ch.FileChannelImpl.write(FileChannelImpl.java:151)
        at FileChannelTest1.positionCheck2(FileChannelTest1.java:69)
        at FileChannelTest1.<init>(FileChannelTest1.java:9)
        at FileChannelTest1.main(FileChannelTest1.java:86)

C:\temp>

5. ダイアログを「了解」すると、ファイルをクローズするかを問い合わせるダイアログが表示される

ファイルをクローズするダイアログ

6. ダイアログはそのままにしておいて、再び FileChannelTest1 を実行

C:\temp>java FileChannelTest1 sample.txt
 


File Opened as Read Write mode.

Size: 200
Position: 0
Read 10 bytes
                                        PLC
 [30, 31, 32, 33, 34, 35, 36, 37, 38, 39]
Position: 10

              以下、略
7. ファイルのクローズのダイアログを「了解」して終了させる。

 

ファイルにロックがかかっている状態だと、他のアプリケーションでそのファイルにアクセスを行うとエラーが発生することがお分かりでしょうか。

ファイルをロックしたときに、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 オブジェクトです。

            // Acquire Lock
            System.out.println("Acquire Lock.");
            FileLock lock = channel.lock();
            System.out.println("Lock: " + lock);

2 行目と 4 行目は単に表示を行っているだけで、実際にロックをかけているのは 3 行目の lock メソッドを使用している部分です。

前述したように lock メソッドはロックがかかるまで処理をブロックします。4 行目の出力結果は次のようになりました。

C:\temp>java FileChannelTest4 sample.txt
Acquire Lock.
Lock: sun.nio.ch.FileLockImpl[0:9223372036854775807 exclusive valid]

FileLock クラスにはいくつかプロパティがあります。それらの値が [ と ] に囲まれている部分です。はじめのコロンで区切られている数字はロックしている部分を示しています。はじめの数字がロックの開始位置で、次の数字がサイズです。

次の "exclusive" というのはロックの種類を表しており、"shared" か "exclusive" のどちらかになります。

最後がロックが有効かどうかを表しています。ロックが解放されていれば "invalid" になります。

ここで出力される値は FileLock クラスの position メソッド、size メソッド、isShared メソッド、isValid メソッドで得ることができます。

このように FileLock クラスにはプロパティがあるのですが、引数なしの lock メソッドではこれらを指定することができません。指定するには引数ありの lock メソッドを使用します。

アプリケーションのソース FileChannelTest5.java

FileChannelTest5 を実行する時には引数として、ファイル名、ロック開始位置、ロックを行うサイズ、シェアードロックかどうかを指定します。

ロックを行っている部分は次のようになっています。

    private void access(String filename, int position, int size, boolean shareFlag){
 
        try{
            RandomAccessFile raFile = new RandomAccessFile(filename, "rw");
            FileChannel channel = in.getChannel();
 
            // Acquire Exclusive Lock
            System.out.println("Acquire Lock.");
            FileLock lock = channel.lock(position, size, shareFlag);
            System.out.println("Lock: " + lock);

最後から 2 行目で、ロックを行っていますが、FileChannelTest4 とは lock メソッドの引数があるかだけの違いになります。

ファイルの 1 部分だけロックをかけることもできるので、次のようなことも可能です。

1. FileChannelTest5 の実行

C:\temp>java FileChannelTest5 sample.txt 0 10 exclusive
Acquire Lock.
Lock: sun.nio.ch.FileLockImpl[0:10 exclusive valid]
Read 10 bytes from position: 0

2. ファイルのロックを解放するかを問い合わせるダイアログが表示される

ロックをリリースするダイアログ

3. ダイアログはそのままにしておく

4. 他の DOS プロンプトで FileChannelTest5 を引数を変化させて実行させる

C:\temp>java FileChannelTest5 sample.txt 20 40 exclusive
Acquire Lock.
Lock: sun.nio.ch.FileLockImpl[20:40 exclusive valid]
Read 40 bytes from position: 20

5. 後から実行した FileChannelTest5 のダイアログが表示される

ロックをリリースするダイアログ

6. 両方とも終了させる。

 

このようにロックする位置を変化させれば、複数のアプリケーションでファイルにアクセスすることができました。

また、同じ部分をロックしてもシェアードロックにすれば、読み込みだけは行うことができます。ダイアログの部分などは同じなので省略します。

1. FileChannelTest5 の実行

C:\temp>java FileChannelTest5 sample.txt 0 200 shared
Acquire Lock.
Lock: sun.nio.ch.FileLockImpl[0:200 shared valid]
Read 200 bytes from position: 0

2. 他の DOS プロンプトで FileChannelTest1 を実行

C:\temp>java FileChannelTest1 sample.txt


File Opened as Read Write mode.

Size: 216
Position: 0
Read 10 bytes
                                        PLC
 [30, 31, 32, 33, 34, 35, 36, 37, 38, 39]
Position: 10

Position changes from 10 to 5

Read 10 bytes
                                        PLC
 [35, 36, 37, 38, 39, 30, 31, 32, 33, 34]
Position: 15

Write 4 bytes
java.io.IOException: プロセスはファイルにアクセスできません。別のプロセスがファ
イルの一部をロックしています。
        at sun.nio.ch.FileDispatcher.write0(Native Method)
        at sun.nio.ch.FileDispatcher.write(FileDispatcher.java:48)
        at sun.nio.ch.IOUtil.writeFromNativeBuffer(IOUtil.java:108)
        at sun.nio.ch.IOUtil.write(IOUtil.java:90)
        at sun.nio.ch.FileChannelImpl.write(FileChannelImpl.java:151)
        at FileChannelTest1.positionCheck1(FileChannelTest1.java:45)
        at FileChannelTest1.<init>(FileChannelTest1.java:8)
        at FileChannelTest1.main(FileChannelTest1.java:86)


File Opened as Append mode.

Size: 216
Position: 0

Write 4bytes

Size: 220
Position: 220

C:\temp>

 

FileChannelTest5 で sample.txt のすべての部分をロックしても、FileChannelTest1 では読み込みだけは行うことができました。しかし、書き込みは例外が発生しています。

 

 
 

最後に

 
 

FileChannel クラスにはここで紹介したように、今までにはなかった便利な機能があります。特に筆者にとってうれしいのはロックがやっとサポートされたことです。

今までは、ロックを行うためにいろいろなトリックを使ってきました、例えば、A というファイルをオープンするときには、別に用意した B というファイルにオープンをしたことを書き込みます。そして、他のクラスが A をオープンしようとするときには B を読み込んで、他のクラスで A を読み込んでいないかどうかを確かめてからオープンするようにする、なんてことをやっていました。この場合、自分で作ったアプリケーションであれば、ファイル B を確かめることができるのですが、一般のアプリケーションではそんなことは知らないので、結局ロックがかけられないという問題がありました。

しかし、そんな問題も今は過去のものとなりました。

ただし、Beta 2 でもまだバグは結構あるようです。例えば、Windows 98 で引数なしの lock メソッドが使えませんでした。引数ありの lock メソッドは使用できるのですけど... その他にも、BugParade で検索するとまだまだバグがありそうです。はやく、安定したバージョンで FileChannel が使えるようになるといいですね。

今回使用したサンプルはここからダウンロードできます。

参考 URL

(Oct. 2001)

 

 
 
Go to Previous Page Go to Contents Go to Java Page Go to Next Page