网页中的编码与乱码(4)

深入介绍了 html 页面使用 BOM 编码的情况,它的优先级为什么最高以及具体的静态页面和动态响应的测试示例。

目录
[隐藏]

这一篇将介绍 BOM 在 html 页面编码中的运用。在最前面曾提到,它的优先级实际上是最高的,在这里,将具体介绍什么是 BOM,还会解析为什么它的优先级最高,然后还会构建一些具体的测试来验证这一点。

什么是 BOM?

关于什么是 BOM,在这篇文章中有详细的介绍:

字符集与编码(七)——BOM

这里也稍微啰嗦几句,内容也基本出自上述文章:BOM=Byte Order Mark,翻译过来就是“字节顺序标识”。

具体则分为两种:小端序Little endian)和大端序Big endian)。

我们知道,在记事本中“另存为”时可以选择编码,有以下几种:

记事本 另存为 编码选择

这里的 Unicode 实际就是 UTF-16(小端序).

注:Java 平台中 UTF-16 缺省为 大端序,与 Windows 恰好相反。

另:记事本的 UTF-8 默认是带 BOM 的,而多数 IDE 的编辑器 UTF-8 默认不带 BOM。

在记事本中以 ANSI 之外的三种编码分别保存一下“hello你好”,分别命名为UTF16BE.txt,UTF16.txt,UTF8.txt(分别对应“Unicode big endian”,“Unicode”,“UTF-8”),查看三个文件的十六进制形式,结果是这样的:

utf16 utf8 bom unicode big endian 大端序 小端序 示例

这三个文件中最前面红框中的字节就是所谓的”BOM“,它不是具体内容”hello你好“的一部分,而是一个额外的特征值。

从上图的红色箭头不难发现,UTF-16 大小端之间,两两字节间顺序恰好相反。对于 UTF-16 字节流而言,开头的 BOM 是必须的,因为它指示了字节流到底是大端序还是小端序,如果弄反了,整个字节流的内容就会面目全非。

而对于 UTF-8 而言呢?实际上却不存在字节序,或者说它只有大端序,就一种字节序。所以,对 UTF-8 而言,BOM 是可以省略的,对于它而言,我们常说的它是“带 BOM”还是“不带 BOM”,而不是说它到底是什么端序。更多时候它的 BOM 是作为字符集编码的标志,因为只要看到这样的 BOM,就知道它是 UTF-8 编码。

下图是各种 BOM 的一个汇总(图片截取自unicode.org):

BOM 汇总 utf8 utf16 utf32

BOM 其实就是 U+FEFF 这一码点,“EF BB BF”就是这一码点在 UTF-8 下的编码。测试代码如下:

utf8 bom 测试代码

为什么 BOM 的优先级最高?

显然,BOM 其实类似于文档内编码声明,不同之处则在于它是由文本编辑器控制写入的,而且是置于文档的最开头处。

假如说你选择了保存为 UTF-8 带 BOM 的编码,文本编辑器就会在内容之前添加“EF BB BF”三个字节,然后之后的内容也一定是用 UTF-8 来编码的。不会发生在“文档内编码声明”中那种错误声明的情况。

这种一致性是由编辑器为我们保证的,除非你直接修改那些最终的二进制的值,否则这种宣称的 BOM 与实际所用的编码的一致性是能得到严格保证的。

换言之,就是它的置信度是最好的,所以它的优先级最高也就不难理解了。

带 BOM 的静态页面测试

如下构建了一个带 BOM 的静态页面,

utf8 bom html 页面

下方编码处的值“UTF-8-BOM”表明了它的实际编码,页面还故意带了一个错误的 meta 声明。

Eclipse 的 编辑器不支持把 UTF-8 保存为带 BOM 的,这个需要用记事本或 notepad++ 这样的编辑器来完成。不过 Eclipse 中的编辑器也能正常识别带 BOM 的 UTF-8 编码的页面。

header 也故意用 gbk 编码来添乱:

mime mapping text/html charset gbk

但由于 BOM 的优先级最高,chrome 浏览器还是能正常打开:

utf8 bom 页面 浏览器 测试

不过在 IE 下的测试却没有通过,它还是采用了 header 中建议的值来解析,从而导致了乱码:

utf8 bom 页面 IE浏览器 测试

但是像 Firefox,Edge 这些较为现代的浏览器都能正常:

utf8 bom 页面 firefox edge 浏览器 测试

表明它们都遵循了 BOM 优先的建议,IE 则是个例外。

这也表明了事情可能要比我们想象的要复杂。

带 BOM 的动态响应测试

同理,也不难构建一个动态的带 BOM 的 UTF-8 响应,同样设置了很多的干扰因素:

utf8 bom 动态页面 示例代码

前面说到:BOM 其实就是 U+FEFF.

浏览器还是能够通过测试:

utf8 bom 动态页面 浏览器测试

用 UTF-16 也是可以的:

utf16 bom 动态页面 示例代码

注意:getBytes(“utf-16”) 生成的字节流数组会自动带上 BOM,这里不需要手动像 UTF-8 那样添加 BOM。前面也说了,对 utf-16 来说,BOM 是必不可少的。

测试也 OK:

utf16 bom 动态页面 浏览器测试

不过如果你明确写成 getBytes(“utf-16le”) 或 getBytes(“utf-16be”),  那么生成的字节流数组就不会带上 BOM 了,这时你要自己在前面加入 BOM,像 UTF-8 时那样:

utf16le bom 动态页面 示例代码

结果也是 OK 的:

utf16le bom 动态页面 浏览器测试

你可以始终用那个转义的 \uFEFF 来表示 BOM,但如果你决定手动输出字节(图上注释代码部分),就要注意小端序时的顺序,否则输出结果将会是面目全非。

关于 BOM 编码的介绍就到这里,最后一篇将谈谈剩下的最后一种情况,也即是“缺省”情况,并最终做个总结。

发表评论

电子邮件地址不会被公开。