NIO を使用した同期非ブロッキングネットワークプログラミング

バッファ(Buffer)とチャネル(Channel)の基本

Java NIO を使ったネットワーク通信では、従来のストリームベースのI/Oとは異なり、バッファとチャネルの概念が中心となります。データは一度バッファに格納され、チャネルを通じて送受信されます。この仕組みにより、非同期・非ブロッキングな処理が可能になります。

主なコンポーネント

  • Buffer:データを一時的に保持するための構造
  • Channel:データの転送経路
  • Selector:複数のChannelを監視し、イベントの発生を検出

1. Buffer の構造と操作

バッファは内部に以下のプロパティを持ちます:
  • capacity: バッファが保持できるデータの最大量
  • position: 次の読み取りまたは書き込みを行う位置
  • limit: 読み取り可能なデータの上限
  • mark: positionの現在値を保存するためのオプション

例:ByteBuffer の基本操作


import java.nio.ByteBuffer;

public class BufferExample {
    public static void main(String[] args) {
        // バッファの作成
        ByteBuffer buffer = ByteBuffer.allocate(512);

        // データの書き込み
        buffer.put("Hello NIO".getBytes());

        // 読み込みモードへの切り替え
        buffer.flip();

        // データの読み込み
        byte[] data = new byte[buffer.limit()];
        buffer.get(data);
        System.out.println(new String(data));
    }
}

直接バッファと非直接バッファ

  • 非直接バッファ: JVMヒープ上に確保される(ByteBuffer.allocate)
  • 直接バッファ: オペレーティングシステムのネイティブメモリ上に確保される(ByteBuffer.allocateDirect)
直接バッファはコピー回数が少なくなりパフォーマンスが向上しますが、生成と破棄のコストが高いため、大容量・長期間使用される場合に適しています。

2. Channel の種類と利用方法

Channel はデータの双方向転送を可能にする接続経路です。NIOでは以下の主要なChannelが提供されています:
  • FileChannel
  • SocketChannel
  • ServerSocketChannel
  • DatagramChannel

ファイルコピーの例


public void copyFile(String source, String target) throws IOException {
    try (FileInputStream fis = new FileInputStream(source);
         FileOutputStream fos = new FileOutputStream(target)) {

        FileChannel in = fis.getChannel();
        FileChannel out = fos.getChannel();

        ByteBuffer buffer = ByteBuffer.allocate(1024);
        while (in.read(buffer) != -1) {
            buffer.flip();
            out.write(buffer);
            buffer.clear();
        }
    }
}

メモリマップドファイルの利用


public void memoryMappedCopy(String source, String target) throws IOException {
    try (FileChannel in = FileChannel.open(Paths.get(source), StandardOpenOption.READ);
         FileChannel out = FileChannel.open(Paths.get(target), StandardOpenOption.WRITE, StandardOpenOption.CREATE)) {

        MappedByteBuffer sourceBuffer = in.map(FileChannel.MapMode.READ_ONLY, 0, in.size());
        MappedByteBuffer destBuffer = out.map(FileChannel.MapMode.READ_WRITE, 0, in.size());

        byte[] data = new byte[sourceBuffer.remaining()];
        sourceBuffer.get(data);
        destBuffer.put(data);
    }
}

transferTo/transferFrom の利用


public void fastCopy(String source, String target) throws IOException {
    try (FileChannel in = FileChannel.open(Paths.get(source), StandardOpenOption.READ);
         FileChannel out = FileChannel.open(Paths.get(target), StandardOpenOption.WRITE, StandardOpenOption.CREATE)) {

        in.transferTo(0, in.size(), out);
    }
}

3. マルチプレクサ Selector の利用

Selector は複数のChannelを監視し、イベントの発生を検出するための仕組みです。ネットワークサーバーなどで多数の接続を効率的に管理するのに使われます。

主なイベント種別

  • OP_CONNECT: 接続完了
  • OP_ACCEPT: 新しい接続要求
  • OP_READ: 読み込み可能
  • OP_WRITE: 書き込み可能

4. データ転送の拡張機能

Scatter/Gather

複数のバッファを使って一度にデータを読み書きする方法です。

public void scatterGatherExample() throws IOException {
    RandomAccessFile file = new RandomAccessFile("data.txt", "rw");
    FileChannel channel = file.getChannel();

    ByteBuffer header = ByteBuffer.allocate(128);
    ByteBuffer body = ByteBuffer.allocate(1024);

    ByteBuffer[] buffers = {header, body};

    // Scatter: 複数バッファに分けて読み込み
    channel.read(buffers);

    // Gather: 複数バッファからまとめて書き込み
    RandomAccessFile output = new RandomAccessFile("output.txt", "rw");
    FileChannel outChannel = output.getChannel();
    outChannel.write(buffers);
}

文字コード変換

Charset クラスを使ってエンコード・デコードが可能です。

public void encodeDecodeExample() throws CharacterCodingException {
    Charset charset = Charset.forName("UTF-8");

    // エンコード
    CharBuffer charBuffer = CharBuffer.wrap("こんにちは世界");
    ByteBuffer byteBuffer = charset.encode(charBuffer);

    // デコード
    CharBuffer decoded = charset.decode(byteBuffer);
    System.out.println(decoded.toString());
}

タグ: NIO SocketChannel Selector Buffer ByteBuffer

8月12日 13:57 投稿