NIO の概要
従来の Java I/O(BIO)は、I/O 操作中にスレッドがブロッキングされる同期モデルです。これにより、多数の同時接続を処理する際にスレッド数が増加し、パフォーマンスのボトルネックが発生します。これを解決するために Java 1.4 で導入されたのが NIO(New I/O)です。NIO は非ブロッキング I/O、バッファ指向、チャネルベースの設計を採用し、少数のスレッドで多数の接続を効率的に処理できます。
ただし、NIO の性能上の利点は高並列・高遅延なネットワーク環境で顕著であり、接続数が少ない場合や低遅延環境では必ずしも BIO より優れているわけではありません。
NIO の主要コンポーネント
NIO は以下の3つの主要コンポーネントで構成されます:
- Buffer(バッファ):データの読み書きを一時的に保持する領域。すべての I/O 操作はバッファ経由で行われます。
- Channel(チャネル):双方向のデータ転送経路。ファイルやソケットなどとの接続を表し、バッファとの間でデータを送受信します。
- Selector(セレクタ):複数のチャネルを単一スレッドで監視するための多路複用機構。イベント駆動型の非ブロッキング I/O を実現します。
BIO がストリームベースであるのに対し、NIO は「チャネルを通じてバッファを運ぶ」モデルです。チャネルはデータそのものに触れず、あくまで輸送路として機能します。また、チャネルは双方向であるため、同一チャネルで読み書きが可能です。
バッファ(Buffer)の詳細
Buffer は抽象クラスであり、実際には ByteBuffer、CharBuffer などの具象クラスが使用されます。特に ByteBuffer はバイトデータの操作に広く使われます。
バッファには以下の4つの重要な内部状態変数があります:
// 状態の関係: 0 <= mark <= position <= limit <= capacity
private int mark = -1; // 復帰位置(任意)
private int position = 0; // 次に読み書きするインデックス
private int limit; // 読み書き可能な上限位置
private int capacity; // バッファの最大容量(不変)
バッファは「書き込みモード」と「読み込みモード」の2つの状態を持ちます。作成直後は書き込みモードで、flip() を呼び出すことで読み込みモードに切り替わります。再び書き込みモードに戻すには clear() または compact() を使用します。
バッファのインスタンスはコンストラクタではなく、静的ファクトリメソッドで生成します。例:
// ヒープ上に割り当て
ByteBuffer heapBuf = ByteBuffer.allocate(1024);
// ダイレクトメモリ(ネイティブメモリ)に割り当て
ByteBuffer directBuf = ByteBuffer.allocateDirect(1024);
主な操作メソッド:
put():データを書き込むget():データを読み取るflip():書き込み→読み込みモードに切り替え(limit = position; position = 0;)clear():全内容を無効化し、書き込みモードにリセット(position = 0; limit = capacity;)
以下は CharBuffer の動作を示す例です:
import java.nio.CharBuffer;
public class BufferExample {
public static void main(String[] args) {
CharBuffer buf = CharBuffer.allocate(8);
printStatus(buf, "初期状態");
buf.put('X').put('Y').put('Z');
printStatus(buf, "3文字書き込み後");
buf.flip();
printStatus(buf, "flip() 呼び出し後");
while (buf.hasRemaining()) {
System.out.print(buf.get());
}
System.out.println();
}
static void printStatus(CharBuffer b, String label) {
System.out.printf("%s → pos=%d, lim=%d, cap=%d%n",
label, b.position(), b.limit(), b.capacity());
}
}