Java NIO 探究字节顺序ByteOrder

概述

在 java.nio 中,字节顺序由 ByteOrder 类封装。

1
2
3
4
5
6
7
8
9
package java.nio;
public final class ByteOrder {
public static final ByteOrder BIG_ENDIAN;
public static final ByteOrder LITTLE_ENDIAN;
public static ByteOrder nativeOrder();
public String toString();
}

ByteOrder 类定义了决定从缓冲区中存储或检索多字节数值时使用哪一字节顺序的常
量。这个类的作用就像一个类型安全的枚举。它定义了以其本身实例预初始化的两个public区域。只有这两个 ByteOrder 实例总是存在于JVM中,因此它们可以通过使用–操作符进行比较。如果您需要知道JVM运行的硬件平台的固有字节顺序,请调用静态类函数nativeOrder()。它将返回两个已确定常量中的一个。调用toString()将返回一个包含两个文字字符串BIG_ENDIAN或者LITTLE_ENDIAN之一的String。
每个缓冲区类都具有一个能够通过调用 order()查询的当前字节顺序设定。

1
2
3
4
5
6
public abstract class CharBuffer extends Buffer
implements Comparable, CharSequence
{
// This is a partial API listing
public final ByteOrder order();
}

这个函数从 ByteOrder 返回两个常量之一。对于除了 ByteOrder 之外的其他缓冲区
类,字节顺序是一个只读属性,并且可能根据缓冲区的建立方式而采用不同的值。除了ByteBuffer,其他通过分配或包装一个数组所创建的缓冲区将从order()返回与ByteOrder.nativeOrder()相同的数值。这使因为包含在缓冲区中的元素在JVM中将会被作为基本数据直接存取。ByteBuffer 类有所不同:默认字节顺序总是ByteBuffer.BIG_ENDIAN,无论系统的固有字节顺序是什么。Java的默认字节顺序是大端字节顺序,这允许类文件等以及串行化的对象可以在任何JVM中工作。如果固有硬件字节顺序是小端,这会有性能隐患。在使用固有硬件字节顺序时,将ByteBuffer的内容当作其他数据类型存取(getLong(),getChar()等方法)很可能高效得多。ByteBuffer的字符顺序设定可以随时通过调用以ByteOrder.BIG_ENDIAN 或ByteOrder.LITTL_ENDIAN 为参数的 order()函数来改变

1
2
3
4
5
6
7
public abstract class ByteBuffer extends Buffer
implements Comparable
{
// This is a partial API listing
public final ByteOrder order( );
public final ByteBuffer order (ByteOrder bo);
}

============================以上为书本摘录,以下为个人探究============================

我们知道,使用BIG_ENDIAN,则会在buffer保存和读取数据时都使用大端字节,如果使用LITTLE_ENDIAN,则会在保存和读取时都使用小端字节,保存和读取使用的字节顺序总是相同的。
有人会想到,可以改变buffer的字节顺序呀,那么,如果在保存后执行改变buffer字节顺序的操作,即调用buffer.order(ByteOrder order),然后再读取,还会是这样吗?其实,在调用buffer.order(ByteOrder order)时,buffer就调整了内部的比特位顺序,即用另一个字节顺序又“保存”了一次,读取时用的也是那个字节顺序,自然也符合“保存和读取使用的是相同的字节顺序”
为了加深使用不同字节顺序的影响,对以下代码的结果进行探究

1
2
3
4
5
6
7
8
9
10
11
ByteBuffer byteBuffer = ByteBuffer.allocate(7).order(ByteOrder.BIG_ENDIAN);
\\ByteBuffer byteBuffer = ByteBuffer.allocate(7).order(ByteOrder.LITTLE_ENDIAN);
CharBuffer charBuffer = byteBuffer.asCharBuffer();
byteBuffer.put(0, (byte) 'A');
byteBuffer.put(1, (byte) 'A');
byteBuffer.put(2, (byte) 0);
byteBuffer.put(3, (byte) 'i');
byteBuffer.put(4, (byte) 0);
byteBuffer.put(5, (byte) '!');
byteBuffer.put(6, (byte) 0);
System.out.println(charBuffer);

大端字节的情况

原始数据:AA0i0!0
大端保存:01000001 01000001 00000000 01101001 00000000 00100001 00000000
大端读取:䅁i!
输出截图:big-end-read.png

解释:”䅁”是”01000001 01000001”按大端读取(从左往右读,16进制的4141H),并使用Unicode解码\u4141的结果,同理,”0069H”得到”i”,”0021H”得到”!”

小端字节的情况

原始数据:AA0i0!0
小端保存:10000010 10000010 00000000 10010110 00000000 10000100 00000000
小端读取:䅁椀\u2100(类似”a/c”的一个符号)
输出截图:little-end-read.png

解释:”䅁”是”10000010 10000010”按小端读取(从右往左读,16进制的4141H),并使用Unicode解码\u4141的结果,同理,”00000000 10010110”按小端读取得到”6900H”,解码得”椀”,”00000000 10000100”读取得到”2100H”,解码得\u2100

总结:

字节顺序对于一个字节的内部比特位顺序并没有影响

即写入是什么字节,读取就是什么字节,因为保存和读取使用的是相同的字节顺序。例如字符”i”的字节(使用Unicode编码并取后8位)为”69H”,那么使用不同字节顺序并不会影响其顺序,不会读取到”96H”,因此,字节顺序对于平时一般的写入和读取没有影响。

字节顺序会影响字节间的顺序,会对视图映射等操作后读取的结果带来影响

例如,原始的”0i”使用大端字节时,读取时解码的是”0069H”,使用小端字节时,读取时解码的是”6900H”,因此解码出来的结果自然也就不一样