上篇主要了解了字符集和字符集编码的相关知识,其中有提到字节序的问题,这篇我们便深入探讨下这方面的知识。
字节序
字节顺序,又称端序或尾序(英语:Endianness)。在计算机科学领域中,是跨越多字节的程序对象的存储规则。在几乎所有的机器上,多字节对象都被存储为连续的字节序列。例如在Java中,一个int类型的变量a地址为0x100,且x
的四个字节将被存储在存储器的0x100, 0x101, 0x102, 0x103
位置。而存储地址内的排列则有两个通用规则。一个多位的整数将按照其存储地址的最低或最高字节排列。如果最低有效位在最高有效位的前面,则称小端序;反之则称大端序。在网络应用中,字节序是一个必须被考虑的因素,因为不同机器类型可能采用不同标准的字节序,所以均按照网络标准转化。
例如假设上述变量x
类型为int
,位于地址0x100
处,它的十六进制为0x01234567
,地址范围为0x100~0x103
字节,其内部排列顺序依赖于机器的类型。大端法从首位开始将是:0x100: 01, 0x101: 23,..
。而小端法将是:0x100: 67, 0x101: 45,..
。
大端序
高位字节在前,低位字节在后,这是人类习惯的读写数值方法。以一个值为0x0A0B0C0D
的int类型变量为例,它的内存为0x100~0x103,则:
内存地址 | 0x100 | 0x101 | 0x102 | 0x103 |
---|---|---|---|---|
存储单元 | 0x0A | 0X0B | 0X0C | 0X0D |
示例中,最高位字节是0x0A 存储在最低的内存地址处。下一个字节0x0B存在后面的地址处。正类似于十六进制字节从左到右的阅读顺序。
小端序
低位字节在前,高位字节在后,现大部分计算机内部处理都是小端序。同样以上面做例子:
内存地址 | 0x100 | 0x101 | 0x102 | 0x103 |
---|---|---|---|---|
存储单元 | 0x0D | 0X0C | 0X0B | 0X0A |
最低位字节是0x0D 存储在最低的内存地址处。后面字节依次存在后面的地址处。
实际意义
到这里我们可以发现,在日常开发中,如果不熟悉字节序的情况下,在涉及字节的读取和解析容易出现问题,那么,为什么要区分大端和小端呢,统一不是更方便吗?
其实这里涉及一个效率的问题,计算机电路先处理低位字节,效率比较高,因为计算都是从低位开始的。如果都是大端序,则计算时需要从高位找到低位,再从低位计算到高位,影响效率。所以,计算机的内部处理都是小端字节序。但是我们人类还是习惯读写大端字节序。所以,除了计算机的内部处理,其他的场合几乎都是大端字节序,比如网络传输和文件储存。
计算机处理字节序的时候,不知道什么是高位字节,什么是低位字节。它只知道按顺序读取字节,先读第一个字节,再读第二个字节。如果是大端字节序,先读到的就是高位字节,后读到的就是低位字节。小端字节序正好相反。只有读取的时候,才必须区分字节序,其他情况都不用考虑。
BOM
我们知道,UTF-16和UTF-32都是多字节的编码规则,那在读取的时候必然也会涉及到字节序的问题,计算机是通过什么判断编码之后的字节序呢,答案就是BOM。
字节顺序标记(byte-order mark,BOM)是位于码点U+FEFF
的统一码字符的名称。当以UTF-16或UTF-32来将UCS/统一码字符所组成的字符串编码时,这个字符被用来标示其字节序。它常被用来当做标示文件是以UTF-8、UTF-16或UTF-32编码的标记。而 FFFE 在 UCS 中是不存在的字符,所以不会出现在实际传输中。UCS 规范建议我们在传输字节流前,先传输BOM标记。这样如果接收者收到 FEFF,就表明这个字节流是大端序的;如果收到 FFFE,就表明这个字节流是小端序的。
UTF-8 不需要 BOM 来表明字节顺序,但可以用 BOM 来表明编码方式。BOM的 UTF-8 编码是 EF BB BF。所以如果接收者收到以 EF BB BF 开头的字节流,就知道这是 UTF-8 编码了。Windows 就是使用 BOM 来标记文本文件的编码方式的。微软在 UTF-8 中使用 BOM 是因为这样可以把 UTF-8 和 ASCII 等编码明确区分开,但这样的文件在 Windows 之外的操作系统里会带来问题。
最后我们再来对比一下携带BOM之后UTF-16编码所得到的结果,其中UTF-16BE代表大端序,UTF-16LE代表小端序: