字节序 — 大端序与小端序
字节序(Byte Order / Endianness)指计算机在内存中存储多字节数据时,字节的排列顺序。
为什么需要字节序?
一个 int 占 4 个字节(32 位),例如十六进制值 0x12345678:
高位字节 低位字节
0x12 0x34 0x56 0x78这 4 个字节在内存中如何排列?这就取决于字节序。
两种字节序
大端序(Big-Endian)
高位字节存储在低地址,即"高位在前"。
内存地址增长方向 →
低地址 高地址
┌──────┬──────┬──────┬──────┐
│ 0x12 │ 0x34 │ 0x56 │ 0x78 │
└──────┴──────┴──────┴──────┘
↑
高位字节值 0x12345678 在内存中按顺序存储为 12 34 56 78。
特点:
- 更符合人阅读数字的习惯(从左到右高位到低位)
- 网络协议中广泛使用
小端序(Little-Endian)
低位字节存储在低地址,即"低位在前"。
内存地址增长方向 →
低地址 高地址
┌──────┬──────┬──────┬──────┐
│ 0x78 │ 0x56 │ 0x34 │ 0x12 │
└──────┴──────┴──────┴──────┘
↑
低位字节值 0x12345678 在内存中存储为 78 56 34 12。
特点:
- 在进行强制类型转换时无需调整字节偏移
- x86/x64 架构使用小端序
对比示例
| 数值 | 大端序存储 | 小端序存储 |
|---|---|---|
0x12345678 | 12 34 56 78 | 78 56 34 12 |
0xDEADBEAF | DE AD BE AF | AF BE AD DE |
0x01020304 | 01 02 03 04 | 04 03 02 01 |
0xAABBCCDD | AA BB CC DD | DD CC BB AA |
检测当前系统的字节序
Java
java
import java.nio.ByteOrder;
public class Endianness {
public static void main(String[] args) {
ByteOrder order = ByteOrder.nativeOrder();
if (order == ByteOrder.BIG_ENDIAN) {
System.out.println("大端序");
} else {
System.out.println("小端序");
}
}
}C/C++
c
#include <stdio.h>
int main() {
unsigned int x = 1;
char *c = (char*)&x;
if (*c) {
printf("小端序\n");
} else {
printf("大端序\n");
}
return 0;
}Python
python
import sys
if sys.byteorder == 'little':
print("小端序")
else:
print("大端序")JavaScript
javascript
const arr = new Uint32Array([0x12345678]);
const view = new Uint8Array(arr.buffer);
if (view[0] === 0x78) {
console.log('小端序');
} else {
console.log('大端序');
}各平台的字节序
| 平台/架构 | 字节序 |
|---|---|
| x86 / x86-64(Intel, AMD) | 小端序 |
| ARM(默认) | 小端序(可配置为大端序) |
| ARM Cortex-M(默认) | 小端序 |
| RISC-V | 小端序 |
| PowerPC | 可配置(通常大端序) |
| SPARC | 大端序 |
| Motorola 68000 | 大端序 |
| Java 虚拟机(JVM) | 大端序(网络字节序) |
| 网络协议(TCP/IP) | 大端序 |
字节序的实际应用
1. 网络字节序
TCP/IP 协议栈规定使用大端序(又称为网络字节序)。
c
#include <arpa/inet.h>
// 主机字节序 → 网络字节序
uint32_t htonl(uint32_t hostlong); // 32位
uint16_t htons(uint16_t hostshort); // 16位
// 网络字节序 → 主机字节序
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);2. 文件格式
许多文件格式对字节序有明确定义:
| 文件格式 | 字节序 |
|---|---|
| JPEG | 大端序 |
| PNG | 大端序(所有整数字段) |
| GIF | 小端序 |
| BMP | 小端序 |
| TIFF | 可指定(头中标识) |
| MP3(ID3 标签) | 小端序 |
| FLV | 大端序 |
| ELF(Linux 可执行文件) | 可配置(头中标识) |
| PE(Windows 可执行文件) | 小端序 |
| Class(Java 字节码) | 大端序 |
Class 文件示例
Java Class 文件使用大端序,魔数 CAFEBABE 按大端序解析:
Class 文件内容(十六进制):
CA FE BA BE 00 00 00 34 ...
解析时直接按顺序读:
CA = 第一个字节(高位)
FE = 第二个字节
BA = 第三个字节
BE = 第四个字节(低位)3. 跨平台数据传输
当不同字节序的系统之间传输二进制数据时,必须进行字节序转换:
发送端(小端序 x86)
┌─────────────────────┐
│ 0x12345678 │
│ 存储: 78 56 34 12 │
└─────────┬───────────┘
│ htonl() 转换为网络字节序(大端)
▼
┌─────────────────────┐
│ 传输: 12 34 56 78 │
└─────────┬───────────┘
│ ntohl() 转换回主机字节序
▼
接收端(小端序 x86)
┌─────────────────────┐
│ 存储: 78 56 34 12 │
│ 0x12345678 │
└─────────────────────┘如果不做转换,接收端会把 12 34 56 78 解释为 0x78563412,导致数据错误。
4. 多字节数据解读
c
#include <stdio.h>
#include <stdint.h>
int main() {
uint8_t buf[4] = {0x12, 0x34, 0x56, 0x78};
// 按大端序解读
uint32_t big = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
printf("大端序: 0x%X\n", big); // 0x12345678
// 按小端序解读
uint32_t little = (buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0];
printf("小端序: 0x%X\n", little); // 0x78563412
return 0;
}大端序 vs 小端序优劣
| 对比项 | 大端序 | 小端序 |
|---|---|---|
| 人类可读性 | 高,数字打印与内存一致 | 低,内存顺序反转 |
| 类型转换 | 固定地址取高位需偏移 | 无需偏移,地址即低位 |
| 网络协议 | 标准(网络字节序) | 需转换 |
| 字符串比较 | 可作为整数直接比较 | 需逐字节比较 |
双端序(Bi-Endian)
某些处理器(如 ARM、PowerPC)支持双端序,可通过寄存器配置切换字节序模式。
c
// ARM 可通过设置 CP15 寄存器切换
// Cortex-M3/M4 通过 AIRCR.ENDIANNESS 位配置常见问题
Q: Java 的字节序是什么?
Java 虚拟机内部使用大端序,且 java.io.DataInputStream / DataOutputStream 也使用大端序读写多字节数据。但 ByteBuffer 可以指定字节序:
java
ByteBuffer buffer = ByteBuffer.allocate(4);
buffer.order(ByteOrder.LITTLE_ENDIAN); // 切换为小端序
buffer.putInt(0x12345678);Q: 为什么 x86 使用小端序?
历史上 Intel 8080 处理器设计时,小端序能让 8 位和 16 位数值在内存中共用地址,简化了硬件设计。这个设计选择延续至今。
Q: 怎么判断一个文件用哪种字节序?
- 查看文件格式规范(大部分文件头会注明)
- 某些格式在文件头中用固定字节标识:如 TIFF 的
0x4949表示小端序,0x4D4D表示大端序 - 文件魔数本身也可以提供线索