问题引入
问:UTF-8 和 UTF-8-BOM 有啥区别?
我们用 Python 演示一下,带与不带 BOM 的字符串有啥区别。
if __name__ == '__main__':
str = '你好啊树哥'
print('str:', str)
str_encode_utf8 = str.encode('utf-8')
print('str_encode_utf8:', str_encode_utf8)
str_encode_utf8_bom = str.encode('utf-8-sig')
print('str_encode_utf8_bom:', str_encode_utf8_bom)
输出结果显示,带 BOM 的比不带的文本在开头多了 ef bb bf
几个字节,为什么是这样?它有什么用呢?
str: 你好啊树哥
str_encode_utf8: b'\xe4\xbd\xa0\xe5\xa5\xbd\xe5\x95\x8a\xe6\xa0\x91\xe5\x93\xa5'
str_encode_utf8_bom: b'\xef\xbb\xbf\xe4\xbd\xa0\xe5\xa5\xbd\xe5\x95\x8a\xe6\xa0\x91\xe5\x93\xa5'
BOM 全称 Byte Order Mark,字节顺序标记的意思,今天我们就来详细聊聊它。
字节序
字节序(Endian),就是字节顺序(Byte-Order),还叫端序或尾序(Endianness)。描述的是计算机对 多字节数据 存储和传输时字节的顺序。因为单字节的数据只需要读取一个字节,没有字节序说法。
字节序分为两种排列模式,分别是大端字节序和小端字节序。
- 大端(Big-endian):高位字节数据存放在内存低地址处,低位字节数据存放在内存高地址处。大端模式比较符合人类的阅读习惯,也这是人类读写数值的方式。
- 小端(Little-endian):高位字节数据存放在内存高地址处,低位字节数据存放在内存低地址处。由于计算机内存数据处理是从低位开始的,因为小端模式更加符合计算机的处理方式。
例如有 0x1234abcd
将它写入到以 0x0000
开始的内存中。
内存地址 | 0x0000 | 0x0001 | 0x0002 | 0x0003 |
---|---|---|---|---|
Big-endian | 0x12 | 0x34 | 0xab | 0xcd |
Little-endian | 0xcd | 0xab | 0x34 | 0x12 |
计算机默认都是从低位开始读,所以计算机内部处理都是小端字节序。但人类习惯读写大端字节序,所以除了计算机的内部处理,其他的应用场合几乎都是大端字节序,比如网络传输和文件存储。
- 主机字节序,Host Byte Order, HBO。指机器的字节序,有大端和小端模式,由机器的CPU处理器的处理决定,小端模式较为常见。
- 网络字节序,Network Byte Order, NBO。是TCP/IP中规定好的一种数据表示格式,网络字节序采用 Big Endian 排列按照从高到低的顺序存储,在网络上使用统一的网络字节顺序可以避免兼容性问题。TCP/IP中规定好了一种数据表示格式,与具体的CPU类型、操作系统等无关,从而保证数据在不同主机之间传输时能够被正确解释。
字节序标记
BOM,全称 Byte Order Mark,字节顺序标记。出现在文本文件头部,用于标识文件是采用哪种格式的编码。
由于 FEFF
和 FFFE
在 UCS 编码表中不存在(也就是说它无实际意义),因此在字节流传输过程中可以利用它们打头来说明字节序。
- 如果接收者收到以
FEFF
开通的字节流,就表明这是 Big-Endian 的; - 如果收到
FFFE
就表明这个字节流是 Little- Endian 的。
像 FEFF
和 FFFE
这种就称作 Zero Width No-Break Space,翻译过来就是 零宽无间断间隔 ,这个字符就是所谓的 BOM。下面是不同编码的字节顺序标记表示。
编码 | 表示 |
---|---|
UTF-8 | EF BB BF |
UTF-16 (大端序) | FE FF |
UTF-16(小端序) | FF FE |
UTF-32 (大端序) | 00 00 FE FF |
UTF-32 (小端序) | FF FE 00 00 |
总结几点注意事项。
- UTF-8 本身不需要 BOM ,但可以使用它。UTF-8 始终是相同的字节顺序,BOM 仅用于表明编码方式。如果接收者收到以
EF BB BF
开头的字节流,就知道这是 UTF-8编码了。 - 以前字符
U+FEFF
出现在开头就是标识该字节流的字节序,如果出现在字节流的中间则表达零宽度非换行空格的意义,用户看起来就是一个空格。从 Unicode 3.2 开始,U+FEFF
只能出现在字节流的开头用于标识字节序。取而代之的是,使用U+2060
来表达零宽度无断空白。 - 刚刚说到的
U+FEFF
是代码点,抽象性质的。而EF BB BF
,FE FF
,00 00 FE FF
则是在不同编码方案下的具体表现形式。这是概念性的问题。
参考
参考了
[1]. Endian
https://www.jianshu.com/p/a348f8fc9fc9
[2]. BOM (字节顺序标记(ByteOrderMark))
https://baike.baidu.com/item/BOM/2790364?fr=aladdin
[3]. 字节顺序标记(BOM)详解
https://blog.csdn.net/gufenchen/article/details/90552774
[4]. 「带 BOM 的 UTF-8」和「无 BOM 的 UTF-8」有什么区别?网页代码一般使用哪个?
https://www.zhihu.com/question/20167122