字节序
字节序(Endian/Byte Order)表示多字节数据的存储规则。字节序分为两种:
- 小端序(Little Endian): 低地址端存储低位字节,高地址端存储高位字节
- 大端序(Big Endian): 低地址端存储高位字节,高地址端存储低位字节
网络传输一般采用大端序,因此我们将大端序也称作网络字节序。对于单个字节的数据,没有字节序的问题。但对于多字节的数据来说,当我们存储或读取这类数据时,往往要面对字节序的问题。
例如,对于 int 型整数 0x01020304 来说,按照小端序存储,在内存中的顺序(从低到高)如下:
04 | 03 | 02 | 01 |
按照大端序存储,在内存中的顺序(从低到高)如下:
01 | 02 | 03 | 04 |
采用哪种字节序?
大端和小端有其各自的优势。大端存储的第一个字节是高位,对于一些数值判断(比如正负)会很迅速;小端存储的第一个字节是低位,符号位在最后一个字节,从低位开始计算,效率比较高。
采用哪种字节序是由硬件厂商决定的,例如我们常用的 x86(包括x86_64)架构的 CPU 采用的是小端序。
下面通过程序验证 CPU 采用的何种字节序:
"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++ 服务器发送信息,就需要处理字节序的问题。