読者です 読者をやめる 読者になる 読者になる

ByteBuffer + OutputStream: 出力ストリームのバイトオーダーを制御する方法

Java

Javaはデフォルトでは入出力でビッグエンディアンを使用するけど、リトルエンディアン或いはその環境にネイティブのバイトオーダーを指定して操作を行ないたい時もある。
今回は出力ストリームのバイトーオーダーを制御する方法のメモ。
基本的にはByteBufferとByteOrderを組み合わせることで制御可能。
詳細は以下のクラスを参照。
※ 下のクラスはXXXOutputStreamという名前になっている癖にOutputStreamをextendsしていないけど、その辺りは必要があれば実装を修正するのは(おそらく)難しくないので放置

import java.io.OutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.Channels;
import java.nio.channels.WritableByteChannel;

public class ByteBufferOutputStream {
    static private final int CAPACITY = 8192; // ByteBufferのバッファサイズ: 適当に大きな任意の値
    
    private WritableByteChannel channel;
    private ByteBuffer buffer;
   
    // sourceStream: ここにコンストラクタ内で指定されたバイトオーダーでデータが出力される。
    public ByteBufferOutputStream(OutputStream sourceStream) {
        channel = Channels.newChannel(sourceStream);
        
        buffer = ByteBuffer.allocate(CAPACITY);

        // 出力ファイルのバイトオーダーを制御
        buffer.order(ByteOrder.nativeOrder()); // ByteOrder.nativeOrder() or ByteOrder.LITTLE_ENDIAN or ByteOrder.BIG_ENDIAN
    }

    // 出力の途中でバイトオーダーを切り替えるためのメソッド
    public void changeByteOrder(int orderType) {
      if(orderType==0) {
        buffer.order(ByteOrder.nativeOrder());   // ネイティブオーダーに
      } else if(orderType > 0) {
        buffer.order(ByteOrder.BIG_ENDIAN);      // ビッグエンディアンに
      } else {
        buffer.order(ByteOrder.LITTLE_ENDIAN);   // リトルエンディアンに
      }
    }

    // バイト配列出力: エンディアンは関係ないけどByteBufferの使用方法のサンプルとして載せておく
    public void write(byte[] bytes) throws IOException {
        for(int offset=0; offset < bytes.length; offset += CAPACITY) {
            final int size = Math.min(CAPACITY, bytes.length-offset);

            buffer.position(0);   // 左の二行はbuffer.clear()で置換可能(おそらく)
            buffer.limit(size);
            buffer.put(bytes, offset, size); // bytesからbufferに読み込み

            buffer.position(0);
            buffer.limit(size);
            channel.write(buffer);           // bufferからchannel(ファイル)に書き出し
        }
    }

    // int値出力: buffer.order(...)で指定したバイトオーダーに従って値を出力する
    public void writeInt(int n) throws IOException {
        buffer.position(0);
        buffer.limit(4);
        buffer.putInt(n);
        
        buffer.position(0);
        buffer.limit(4);
        channel.write(buffer);
    }

    public void close() throws IOException {
        channel.close();
    }
}