バッファ(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());
}