初出 JAVA PRESS Vol.29

Java API ダイジェスト

java.nio.channels.ByteChannel

Channelインタフェースはファイルやソケットなどのコネクションを表します。しかし、単体では読み書き機能の定義はされていません。読み込みを定義しているのがReadableByteChannelインタフェース、書き込みはWritableByteChannelインタフェースで定義されています。

ByteChannelはこれら2つのインタフェースの派生インタフェースで読み込み・書き込みの両方が行えるチャネルです。

ByteChannelを実装したクラスにはFileChannelクラス、SocketChannelクラスなどがあります。


読み書き両用チャネル

ByteChannelインタフェースは読み込みと書き込みのメソッドをまとめています。チャネルが読み書きするのはバイト列で、ByteBufferクラスを使用して表します。

読み込みにはReadableByteChannelインタフェースで定義されているreadメソッドを使用します。引数はByteBufferオブジェクトです。

読み込み

書き込みにはWritableByteChannelインタフェースで定義されているwriteメソッドを使用します。引数はreadメソッドと同様にByteBufferオブジェクトです。

書き込み

チャネルのクローズはChannelインタフェースで定義されているcloseメソッドを使用します。

クローズ

読み込み・書き込みするのなら

読み込み

public int read(ByteBuffer dst) throws IOException

チャネルからバッファに読み込みを行います。読み込んだ結果はByteBufferオブジェクトのdstに格納されます。格納されるのはdstのpositionからlimitまでです。

readメソッドの戻り値は読み込んだバイト数です。チャネルが最後に達したら戻り値は-1になります。

ストリームでは読み込みを行っているときに、他のスレッドからストリームがクローズされたりインターラプトされた時の動作が明確になっていませんでしたが、New I/Oではこのような事象に対する例外が定義されています。

図1に非同期クローズのシーケンスを示します。非同期クローズされた場合、AsynchronousCloseException例外が発生します。同様に他のスレッドからThread#interruptメソッドを使用してインターラプトされたときにはClosedByInterruptException例外が発生します。

また、クローズしているチャネルに対してreadメソッドをコールした場合、ClosedChannelException例外が発生します。

ただし、これらの例外はすべてIOExceptionクラスの派生クラスなので、IOExceptionに対応する例外処理を記述するだけでもかまいません。

AsynchrousCloseException

図1 AsynchrousCloseException

書き込み

public int write(ByteBuffer src) throws IOException

バッファの内容をチャネルに書き込みます。書き込むのはsrcのpositionからlimitまでです。writeメソッドを抜けるとsrcのpositionは書き込んだバイト数だけ移動します。

戻り値は書き込んだバイト数です。

writeメソッドもreadメソッドと同様に、非同期クローズや非同期インターラプトされたときにはAsynchronousCloseException例外、ClosedByInterruptException例外が発生します。また、チャネルがクローズしていた場合もreadメソッドと同じで、ClosedChannelException例外が発生します。

リスト1にreadメソッドとwriteメソッドを使ったファイルのコピーの例を示します(一部、例外処理を省略してあります)。この例ではByteChannelインタフェースをインプリメントしているFileChannelクラスを使用しています。

例ではwhileループの中で1000バイトづつreadメソッドを使用して読み込みます。読み込んだ結果はByteBufferオブジェクトに格納されるので、それをwriteメソッドを使用して書き込んでいます。

readメソッドで読み込んだときにbufferのlimitまでデータが書き込まれなくとも、ByteBuffer#flipメソッドを使用すれば、bufferに書きこまれた位置までlimitが移動し、positionは0に移動します。writeメソッドはByteBufferオブジェクトのpositionからlimitまでを書き込むので、読み込んだバイト数を気にせずに書き込みを行うことができます。

リスト 1 readメソッド、writeメソッドの使用例

    ByteChannel readChannel = new RandomAccessFile(dstFilename, "r").getChannel();
    ByteChannel writeChannel = new RandomAccessFile(srcFilename, "w").getChannel();
    ByteBuffer buffer = ByteBuffer.allocate(1000);
    while (true) {
        buffer.clear();
        try {
            if (readChannel.read(buffer) == -1) {
                break;
            }
            buffer.flip();
            writeChannel.write(buffer);
        } catch (AsynchronousCloseException ex) {
            // 非同期クローズに対応する例外処理
        } catch (ClosedByInterruptException ex) {
            // 非同期インターラプトに対応する例外処理
        } catch (ClosedChannelException ex) {
            // クローズしたチャネルに処理を行った場合の例外処理
        } catch (IOException ex) {
            // IOException に対応する例外処理
        }
    }
    readChannel.close();
    writeChannel.close();

クローズ

public void close() throws IOException

チャネルのクローズを行います。チャネルをクローズすると、もとのストリームもクローズされます。

チャネルはマルチスレッドでの使用を考慮されています。このため、複数のスレッドでチャネルを使用しているときに、他のスレッドで読み込み・書き込みを行っているときにクローズをしてしまうと、読み込み・書き込みを行っているスレッドでAsynchronousCloseException例外が発生します。

コラム より大容量の入出力

ByteChannelはByteBufferを使用して、高速に読み込み・書き込みを行うことができます。しかし、より大容量のデータの入出力を行うためのインタフェースもNew I/Oには用意されています。

ScatteringByteChannelインタフェースが読み込み、GatheringByteChannelインタフェースが書き込みを行います。双方ともメソッドが

    public long read(ByteBuffer[] buffer) throws IOException
    public long write(ByteBuffer[] buffer) throws IOException

のようにByteBufferの配列になっており、一度に大量のデータを読み込み・書き込みすることができます。

FileChannelやSocketChannelはByteChannelインタフェースだけでなく、この2つのインタフェースを実装しているので入出力するデータの量に応じて使い分けることができます。

(2003.03)