字节序

字节序(Endian/Byte Order)表示多字节数据的存储规则。字节序分为两种:

  • 小端序(Little Endian): 低地址端存储低位字节,高地址端存储高位字节
  • 大端序(Big Endian): 低地址端存储高位字节,高地址端存储低位字节

网络传输一般采用大端序,因此我们将大端序也称作网络字节序。对于单个字节的数据,没有字节序的问题。但对于多字节的数据来说,当我们存储或读取这类数据时,往往要面对字节序的问题。

例如,对于 int 型整数 0x01020304 来说,按照小端序存储,在内存中的顺序(从低到高)如下:

04

03

02

01

按照大端序存储,在内存中的顺序(从低到高)如下:

01

02

03

04


采用哪种字节序?

大端和小端有其各自的优势。大端存储的第一个字节是高位,对于一些数值判断(比如正负)会很迅速;小端存储的第一个字节是低位,符号位在最后一个字节,从低位开始计算,效率比较高。

采用哪种字节序是由硬件厂商决定的,例如我们常用的 x86(包括x86_64)架构的 CPU 采用的是小端序。

下面通过程序验证 CPU 采用的何种字节序:

@SuppressWarnings("restriction")
public class ByteOrder {
public static void main(String[] args) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);
long a = unsafe.allocateMemory(8);
try {
unsafe.putLong(a, 0x0102030405060708L);
byte b = unsafe.getByte(a);
switch (b) {
case 0x01:
System.out.println("大端序");
break;
case 0x08:
System.out.println("小端序");
break;
default:
assert false;
}
} finally {
unsafe.freeMemory(a);
}
}
}

输出结果:

小端序

可以看出,电脑的 CPU 采用的是小端序。

注意,程序中用的是 sun.misc.Unsafe 类,这个类是非公有API,Oracle 公司已经不提供它的源码了,但我们可以下载 OpenJDK 源码来查看。这里是通过反射的方式获取 Unsafe 对象(参考:sun.misc.Unsafe的理解)。如果在 Eclipse 中运行的话,还需要设置允许使用限制API。

更简单的是采用 Java 自带的方法输出字节序,如下所示:

System.out.println(ByteOrder.nativeOrder());

JVM字节序

JVM规范规定了 Class 文件采用大端字节序。Java标准库所支持的序列化格式,跟 Class 文件一样,也是规定用大端方式存储多字节数据。这就使得在 x86 上用 Java 自带的序列化/反序列化功能也要经过大小端转换


什么时候需要转换字节序?

虽然大小端的问题在内存、软件等地方存在,但在下面两种情况下不需要关注这个问题:

  • 在单机上,使用同一种编程语言,读写变量或文件、进行网络通信
  • 在分布式场景下,使用同一种编程语言,在大小端模式相同的机器之间传输信息

当不符合上面条件时,比如通过 Java 向一个 C++ 服务器发送信息,就需要处理字节序的问题。