在各种计算机体系结构中,对于字节、字等的存储机制有所不同,因而引发了计算机通信领域中一个很重要的问题,即通信双方交流的信息单元(比特、字节、字、双字等等)应该以什么样的顺序进行传送。如果不达成一致的规则,通信双方将无法进行正确的编/译码从而导致通信失败。
1980 年,Danny Cohen 在其著名的论文”On Holy Wars and a Plea for Peace”中为了平息一场关于在消息中字节该以什么样的顺序进行传送的争论而引用了该词。该文中,Cohen 非常形象贴切地把支持从一个消息序列的最高位开始传送的那伙人叫做 Big-Endians,支持从最低位开始传送的相对应地叫做 Little-Endians。此后 Endian 这个词便随着这篇论文而被广为采用。
首先,明确一点,咱们接触到的物理单元最小都是字节;因此,无论是 big endian,还是 little endian,都是针对多个字节的序列而言的;当然,在通信领域中,这里往往是 bit,不过原理也是类似的
对于字节序列的存储格式,目前有两大阵营,那就是 Motorola 的 PowerPC 系列 CPU 和 Intel 的 x86 系列 CPU。PowerPC 系列采用 big endian 方式存储数据,而 x86 系列则采用 little endian 方式存储数据。那么究竟什么是 big endian,什么又是 little endian 呢?
Little-endian:将低序字节存储在起始地址(低位编址)
Big-endian:将高序字节存储在起始地址(高位编址)
案例:
注:每个地址存 1 个字节,2 位 16 进制数是 1 个字节(0xFF=11111111)
上面的图已经够直观了。也就是说:
各自的优势:
如果你写的程序只在单机环境下面运行,并且不和别人的程序打交道,那么你完全可以忽略字节序的存在。
但是,如果你的程序要跟别人的程序产生交互呢? 比如,当一个 C/C++ 的程序要与一个 Java 程序交互时:
C/C++语言编写的程序里数据存储顺序是跟编译平台所在的 CPU 相关的,而现在比较普遍的 x86 处理器是 Little Endian
JAVA 编写的程序则唯一采用 Big Endian 方式来存储数据
Go 编写的程序可自主选择使用哪个字节序,有开发者来约定
试想,如果你的 C/C++程序将变量 a = 0x12345678 的首地址传递给了 Java 程序,由于 Java 采取 Big Endian 方式存储数据,很自然的它会将你的数据翻译为 0x78563412。显然,问题就出现了!!!
另外,网络传输一般采用 Big Endian,也被称之为网络字节序,或网络序。当两台采用不同字节序的主机通信时,在发送数据之前都必须经过字节序的转换成为网络字节序后再进行传输。
网络字节序: TCP/IP 各层协议将字节序定义为 Big Endian,因此 TCP/IP 协议中使用的字节序是大端序。
主机字节序: 整数在内存中存储的顺序,现在 Little Endian 比较普遍。(不同的 CPU 有不同的字节序)
在进行网络通信时 通常需要调用相应的函数进行主机序和网络序的转换。
Go 中处理大小端序的代码位于 encoding/binary
包,其中全局变量 BigEndian
用于操作大端序数据,LittleEndian
用于操作小端序数据,这两个变量所对应的数据类型都实行了 ByteOrder
接口:
type ByteOrder interface {
Uint16([]byte) uint16
Uint32([]byte) uint32
Uint64([]byte) uint64
PutUint16([]byte, uint16)
PutUint32([]byte, uint32)
PutUint64([]byte, uint64)
String() string
}
其中,前三个方法用于读取数据,后三个方法用于写入数据。
大家可能会注意到,上面的方法操作的都是无符号整型,如果我们要操作有符号整型的时候怎么办呢?很简单,强制转换就可以了,比如这样:
func PutInt32(b []byte, v int32) {
binary.BigEndian.PutUint32(b, uint32(v))
}
为了程序的兼容,我们在开发跨服务器的 TCP 服务时,每次发送和接受数据都要进行转换,这样做的目的是保证代码在任何计算机上执行时都能达到预期的效果。
参考:
go 语言的字节序
Big Endian 和 Little Endian 详解
字节序:Big Endian 和 Little Endian