大端小端

字节序

字节序(Byte Order),指的是多字节数据类型,在内存中存储的顺序
计算机系统,采用字节作为逻辑寻址单位。在处理多字节数据类型时,就要区分字节序。

Big-Endian、Little-Endian

  • Big-Endian 大端(高位字节在前)

低位字节存放在内存的高地址端,高位字节存放在内存的低地址端。
低地址存放最高有效位(MSB)

  • Little-Endian 小端(低位字节在前)

低位字节存放在内存的低地址端,高位字节存放在内存的高地址端。
低地址存放最低有效位(LSB)

说明

  • 大端小端的概念,是面向多字节数据类型的内存存储方式定义的,比如2字节、4字节、8字节的整型、长整型、浮点型等,单字节没有字节序的问题。
  • “前” 是指靠近内存低地址(起始地址),存储在硬盘上就是先写那个字节。
  • 内存地址生长方向为: 从前(左)到后(右),由低地址到高地址 (这是不变的)。
  • 大端直观,因为与现实生活中,数字书写形式一致: 从前(左)到后(右),由高位到低位。
    小端符合人的思维,低位值小,就应该放在内存地址小的地方,高位值大就应该放在内存地址大的地方。

举个栗子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

4 字节整数为例: 0x12345678

Big-Endian

低地址 高地址
-------------------->
+-+-+-+-+-+-+-+-+-+-+
| 12 | 34 | 56 | 78 |
+-+-+-+-+-+-+-+-+-+-+

Little-Endian

低地址 高地址
-------------------->
+-+-+-+-+-+-+-+-+-+-+
| 78 | 56 | 34 | 12 |
+-+-+-+-+-+-+-+-+-+-+

MSB、LSB

  • 最高有效位 MSB: Most Significant Bit

最高有效位(MSB),有时候叫做最左边的位,是在一个n位二进制数字中的n-1位,这个位有最高的权重(2^(n-1))。第一个或最左边的位,当这个数字被用一般的方式书写时。

  • 最低有效位 LSB: Least Significant Bit

最低有效位(LSB)是给这些单元值的一个二进制整数位位置,就是,决定是否这个数字是偶数或奇数。LSB有时候是指最右边的位,因为写较不重要的数字到右边位置符号的协定。它类似于一个十进制整数的最不重要的数字,它是在一个(最右边)位置的数字。

HBO、NBO

主机字节序

主机字节序(HBO,Host Byte Order),就是上文介绍的,多字节数据类型,在内存中存储的顺序
其它详见上文。

不同的机器 HBO 不相同,与 CPU 设计有关:
Intel x86,采用小端字节序
IBM PowerPC,采用大端字节序

网络字节序

网络字节序(NBO,Network Byte Order),是网络传输时的字节序。

网络传输的数据,是字节流。

对一个多字节数据来说,在进行网络传输的时候,发送方先传递高位字节还是低位字节?
接收方收到第一个字节的时候,它将这个字节作为高位字节还是低位字节处理?

它是 TCP/IP 协议中规定好的一种数据表示格式,它与具体的CPU类型、操作系统等无关,保证了数据在不同主机之间传输时能够被正确读取。

网络字节序,采用 Big-Endian 大端字节序,也就是高位字节先走

以 4 字节整数为例: 0x12345678,先发送 12,再依次发送 34,56,78。

Java 字节序

C/C++ 语言编写的程序里,字节序是跟编译平台所在的 CPU 相关。
Java 是平台无关的,JVM 为我们屏蔽了大量的底层细节和复杂性。

Java 默认字节序,是 大端字节序

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public final class ByteOrderTest {

public static void main(String[] args) {
System.out.println("Native Byte Order: " + ByteOrder.nativeOrder());// LITTLE_ENDIAN

ByteBuffer byteBuffer = ByteBuffer.allocate(4);
System.out.println("JVM Default Byte Order: " + byteBuffer.order());// BIG_ENDIAN
byteBuffer.putInt(0x12345678);
printByteBuffer(byteBuffer);// BIG_ENDIAN: 12 34 56 78

byteBuffer.clear();
byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
byteBuffer.putInt(0x12345678);
printByteBuffer(byteBuffer);// LITTLE_ENDIAN: 78 56 34 12
}

private static void printByteBuffer(ByteBuffer byteBuffer) {
StringBuilder sb = new StringBuilder().append(byteBuffer.order()).append(":");
byteBuffer.flip();
for (int i = 0; i < byteBuffer.limit(); i++) {
byte b = byteBuffer.get(i);
sb.append(" ").append(Integer.toHexString(b));
}
System.out.println(sb);
}
}

ByteBuffer

1
2
3
4
5
6
7
8
9
10
11
12
public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer> {
// ...

// 默认是 BIG_ENDIAN
boolean bigEndian = true;

public final ByteOrder order() {
return bigEndian ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN;
}

// ...
}

ByteOrder

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public final class ByteOrder {

private String name;

private ByteOrder(String name) {
this.name = name;
}

// In this order, the bytes of a multibyte value are ordered from most significant to least significant.
public static final ByteOrder BIG_ENDIAN = new ByteOrder("BIG_ENDIAN");

// In this order, the bytes of a multibyte value are ordered from least significant to most significant.
public static final ByteOrder LITTLE_ENDIAN = new ByteOrder("LITTLE_ENDIAN");

/**
* Retrieves the native byte order of the underlying platform.
*
* This method is defined so that performance-sensitive Java code can
* allocate direct buffers with the same byte order as the hardware.
* Native code libraries are often more efficient when such buffers are used.
*
* @return The native byte order of the hardware upon which this Java
* virtual machine is running
*/
public static ByteOrder nativeOrder() {
return Bits.byteOrder();
}

}

Bits

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
* Access to bits, native and otherwise.
*/
class Bits {// package-private

private Bits() {}

// -- Processor and memory-system properties --

private static final ByteOrder byteOrder;

static {
long a = unsafe.allocateMemory(8);
try {
unsafe.putLong(a, 0x0102030405060708L);
byte b = unsafe.getByte(a);
switch (b) {
case 0x01: byteOrder = ByteOrder.BIG_ENDIAN; break;
case 0x08: byteOrder = ByteOrder.LITTLE_ENDIAN; break;
default:
assert false;
byteOrder = null;
}
} finally {
unsafe.freeMemory(a);
}
}

static ByteOrder byteOrder() {
if (byteOrder == null)
throw new Error("Unknown byte order");
return byteOrder;
}

// ...
}

附: 词源

Endian 这个词,来源于 Jonathan Swift 在 1726 年写的讽刺小说 “Gulliver’s Travels”(《格利佛游记》)。

小说描述了 Gulliver 畅游小人国时,碰到了如下的一个场景。
小人国里的小人,因为非常小(身高6英寸),所以总是碰到一些意想不到的问题。
有一次因为对水煮蛋该从大的一端(Big-End)剥开,还是小的一端(Little-End)剥开,引发了一场战争,并形成了两支截然对立的队伍。
支持从 Big-End 剥开的人,Swift 就称作 Big-Endians,支持从 Little-End 剥开的人,就称作 Little-Endians(后缀 ian 表明的就是支持某种观点的人:-)。

Endian这个词由此而来。

Danny Cohen 一位网络协议的开创者,第一次使用这两个术语指代字节序,后来就被大家广泛接受。

参考

本文参考了互联网上大家的分享,就不一一列举,在此一并谢过。
也希望本文,能对大家有所帮助,若有错误,还请谅解、指正。