バイト列を扱う際、バイトオーダーを意識しないといけない場合があります。
このような場合、Javaでは、ByteBufferクラスを使用すると便利です。ByteBufferにはバイトオーダーを設定でき、複数バイトから構成される値を簡単に扱えるためです。例えば、次のコードでは、リトルエンディアンの値を扱っています。
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class LittleEndianSample {
public static final void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(Short.BYTES);
// リトルエンディアンの16ビット整数。下位バイトが0x11、上位バイトが0x22。
buffer.order(ByteOrder.LITTLE_ENDIAN)
.put((byte) 0x11)
.put((byte) 0x22)
.rewind();
System.out.printf("0x%x\n", buffer.getShort());
}
}
実行結果は次の通りです。
0x2211
ところが、上記のByteBufferを複製すると、バイトオーダーが想定外のものになってしまいます。例えば、リトルエンディアンのByteBufferを返すメソッドを書いているとします。その呼び出し元がByteBufferの内容を変更できないように、読み取り専用で返すことにします。しかし、次のようなコードでは、呼び出し元がビッグエンディアンのByteBufferを受け取ってしまいます。
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class BadSample {
private static class BufferObject {
private final ByteBuffer buffer;
public BufferObject() {
buffer = ByteBuffer.allocate(Short.BYTES);
// リトルエンディアンの16ビット整数。下位バイトが0x11、上位バイトが0x22。
buffer.order(ByteOrder.LITTLE_ENDIAN)
.put((byte) 0x11)
.put((byte) 0x22)
.rewind();
}
private ByteBuffer getBuffer() {
// 読み取り専用で複製(誤り。ビッグエンディアンになる)
return buffer.asReadOnlyBuffer();
}
}
public static final void main(String[] args) {
BufferObject bufferObject = new BufferObject();
ByteBuffer buffer = bufferObject.getBuffer();
System.out.printf("0x%x\n", buffer.getShort());
}
}
実行結果は次のようになります。上位バイトと下位バイトとが入れ替わり、ビッグエンディアンになっていることがわかります。
0x1122
なぜこのような挙動となるのでしょうか。
OpenJDKのIssue Trackerに投稿されたIssue JDK-7178109によると、この挙動は仕様とのことです。
新規に作成されたByteBufferのバイトオーダーは、常にビッグエンディアンとなります。つまり、allocate()やallocateDirect()以外にも、下記のメソッドから返されるByteBufferのバイトオーダーは、元のByteBufferのバイトオーダーにかかわらず、常にビッグエンディアンです。
- duplicate()
- asReadOnlyBuffer()
- wrap()
- slice()
- alignedSlice()
Java SEでは、バージョン9のAPIドキュメントから、上記の各メソッドに、作成されたByteBufferのバイトオーダーがビッグエンディアンになることが記されています。しかし、AndroidのAPIドキュメントでは、現時点(2019/7/9)ではそのような記述はありません。
以上から、バイトオーダーを保ったままByteBufferを複製するには、複製後にorder()でバイトオーダーを再度設定する必要があります。
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class DuplicationSample {
private static class BufferObject {
private final ByteBuffer buffer;
public BufferObject() {
buffer = ByteBuffer.allocate(Short.BYTES);
// リトルエンディアンの16ビット整数。下位バイトが0x11、上位バイトが0x22。
buffer.order(ByteOrder.LITTLE_ENDIAN)
.put((byte) 0x11)
.put((byte) 0x22)
.rewind();
}
private ByteBuffer getBuffer() {
// 読み取り専用で複製。バイトオーダーを設定する必要がある。
return buffer.asReadOnlyBuffer().order(buffer.order());
}
}
public static final void main(String[] args) {
BufferObject bufferObject = new BufferObject();
ByteBuffer buffer = bufferObject.getBuffer();
System.out.printf("0x%x\n", buffer.getShort());
}
}
実行結果は次の通りです。バイトオーダーが正しくリトルエンディアンになりました。
0x2211