大端和小端

字节序

字节顺序,又称之为:端序尾序。在计算机科学领域中指的是:电脑内存 或者在网络通讯链路中,由多个字节组成的的排列方式。

由于不同架构的CPU处理多个字节数据的顺序不一样,比如x86的是小段端模式,KEIL C51是大端模式。但是后来互联网流行,TCP/IP协议规定为大端模式,又称为:network order。

大端和小端

在计算机体系中我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为8bit。
在编程语言中一个long类型占32bit,那就需要4个字节来存储,那按照什么样的顺序将这四个字节写到内存中?
因此就出现了大端存储模式和小端存储模式。

大端(Big-Endian): 数据的高位字节在前(内存的低地址),低位字节在后。这样的存储模式类似于把数据当做字符串处理,内存地址由小到大增长,而数据从高位字节开始写入。

这种方式符合人类的阅读习惯!

小端(Little-Endian): 数据的低位字节在前(内存的低地址),高位字节在后。这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低。

示例:

如:
32位的16进制数:0x1A2B3C4D 在内存中的存储

1
2
3
4
内存地址: 00  01  02  03
------------------------------
小端模式: 4D 3C 2B 1A
大端模式: 1A 2B 3C 4D

java中的大端和小端

Java二进制文件中的所有内容均按大端顺序存储。这种存储方式也被称为network order。这意味着,如果仅仅是用java,则在所有的平台上(如Mac、 PC、 UNIX等)
所有文件的处理方式都相同,可以自由的进行二进制文件的交换,而无需担心字节顺序的问题。

java对我们隐藏了内部字节顺序的问题!

但是当我们与某些不是用Java编写的使用小端顺序的程序交换数据文件时,就会出现问题。最常见的是使用C编写的程序。某些平台在内部使用大端顺序(Mac,IBM 390),有些使用小端序(Intel)。

C语言默认是小端模式。
如果java 要读取C 写的二进制文件,就要涉及到大小端转换的问题。


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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

public static void printSystemInfo() {
System.out.println("##########################################");
System.out.println("系统名称:" + System.getProperty("os.name"));
System.out.println("系统架构:" + System.getProperty("os.arch"));
System.out.println("系统版本:" + System.getProperty("os.version"));
System.out.println("##########################################");
System.out.println("");
}

public static Unsafe getUnsafe() throws Exception {
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);
return unsafe;
}

public static void main(String[] args) throws Exception {
printSystemInfo();
try {
Unsafe UNSAFE = getUnsafe();

int intVal = 0x1A2B3C4D;
System.out.println("原始值:十六进制:" + Integer.toHexString(intVal) + " 对应十进制:" + intVal);

// 分配4个字节的内存
long address = UNSAFE.allocateMemory(4);
// 存放int类型的数据,占4个字节
UNSAFE.putInt(address, intVal);
byte b = UNSAFE.getByte(address);
// 通过getByte方法获取刚才存放的int,取第一个字节
// 如果是大端,存放顺序 —> 1A,2B,3C,4D,取第一位便是0x1A
// 如果是小端,存放顺序 —> 4D,3C,2B,1A ,取第一位便是0x4D
System.out.println("取到的第一个字节:" + Integer.toHexString(b));
ByteOrder byteOrder;
switch (b) {
case 0x1A:
System.out.println("当前使用:大端序");
byteOrder = ByteOrder.BIG_ENDIAN;
break;
case 0x4D:
System.out.println("当前使用:小端序");
byteOrder = ByteOrder.LITTLE_ENDIAN;
break;
default:
byteOrder = null;
}
System.out.println(byteOrder);
// 这里在X86架构的windows机器上跑,输出结果为:
// LITTLE_ENDIAN

// 然后我们重新从内存中读取int
int val2 = UNSAFE.getInt(address);
System.out.println("重新从内存中读取的值:" + val2);
} catch (Exception e) {
e.printStackTrace();
}
}

输出结果

window平台
1
2
3
4
5
6
7
8
9
10
11
##########################################
系统名称:Windows 7
系统架构:amd64
系统版本:6.1
##########################################

原始值:十六进制:1a2b3c4d 对应十进制:439041101
取到的第一个字节:4d
当前使用:小端序
LITTLE_ENDIAN
重新从内存中读取的值:439041101
linux平台
1
2
3
4
5
6
7
8
9
10
11
##########################################
系统名称:Linux
系统架构:amd64
系统版本:2.6.32-642.el6.x86_64
##########################################

原始值:十六进制:1a2b3c4d 对应十进制:439041101
取到的第一个字节:4d
当前使用:小端序
LITTLE_ENDIAN
重新从内存中读取的值:439041101