12
1 / 12 UTF-8UTF-16UTF-32 以及 BOM 英原文:http://unicode.org/faq/utf_bom.html 译者博客:http://hi.baidu.com/zsjforcn/blog/ 涉及 UTF 或编码形式的一般性问题 QUnicode 16 位编码的吗? A:不是。1991 年到 1995 年第一版 Unicode 确实是 16 位编码,但自从 Unicode2.0 1996 7 月)它再也不是 16 位编码了。Unicode 标准编码字符集从 U+0000 U+10FFFF,总共有 21 位编码空间。根据你自己的需要选择不同的编码形式: UTF-8UTF-16 或者 UTF-32,每个字符会被表示成 1 4 8 位字节,1 2 16 位字或 1 32 位双字。 QUnicode 文本可以有多种表示吗? A:可以。有多种表示 Unicode 数据的方式,包括 UTF-8UTF-16 UTF-32。另外, 还有一些压缩转换的方式,例如,Unicode 技术标准#6Unicode 的一种标准压缩 方案SCSU)。 Q:什么是 UTFAUnicode 转换格式(UTF)是一种算法,它将每个 Unicode 编码点(除了代理编 码点)映射到一个唯一的字节序上。 每种 UTF 都是可逆的。故,每种 UTF 都支持“无损压缩行程”:将任何 Unicode 编码的字符 S 映射到一串字节序上,而反过来也会生成 S。为了保证此行程,UTF 映射“必须”也将所有不合法的 Unicode 字符编码点映射到唯一的字节序上。这些 非法的编码点包含了 66 个非字符(从 FFFE FFFF)和其他代理。 虽然 SCSU 压缩方式是可逆的,但它不是 UTF,因为依赖于不同的 SCSU 压缩器,同 样的字符串可以映射到多个不同的字节序上。 Q:关于编码形式可以在哪里获得更多的信息? AUTF 正式定义见 Unicode 标准 3.9 节:Unicode 编码形式。关于编码形式的更多 信息见 Unicode 技术报告 UTR#17Unicode 字符编码模型Q:如何写一个 UTF 转换器?

Utf bom faq

Embed Size (px)

Citation preview

Page 1: Utf bom faq

1 / 12

UTF-8、UTF-16、UTF-32 以及 BOM

英原文:http://unicode.org/faq/utf_bom.html

译者博客:http://hi.baidu.com/zsjforcn/blog/

涉及 UTF 或编码形式的一般性问题

Q:Unicode 是 16 位编码的吗?

A:不是。1991 年到 1995 年第一版 Unicode 确实是 16 位编码,但自从 Unicode2.0

(1996 年 7 月)它再也不是 16 位编码了。Unicode 标准编码字符集从 U+0000 到

U+10FFFF,总共有 21 位编码空间。根据你自己的需要选择不同的编码形式:UTF-8,

UTF-16 或者 UTF-32,每个字符会被表示成 1 到 4 个 8 位字节,1 到 2 个 16 位字或

者 1 个 32 位双字。

Q:Unicode 文本可以有多种表示吗?

A:可以。有多种表示 Unicode 数据的方式,包括 UTF-8,UTF-16 和 UTF-32。另外,

还有一些压缩转换的方式,例如,Unicode 技术标准#6:Unicode 的一种标准压缩

方案(SCSU)。

Q:什么是 UTF?

A:Unicode 转换格式(UTF)是一种算法,它将每个 Unicode 编码点(除了代理编

码点)映射到一个唯一的字节序上。

每种 UTF 都是可逆的。故,每种 UTF 都支持“无损压缩行程”:将任何 Unicode

编码的字符 S 映射到一串字节序上,而反过来也会生成 S。为了保证此行程,UTF

映射“必须”也将所有不合法的 Unicode 字符编码点映射到唯一的字节序上。这些

非法的编码点包含了 66 个非字符(从 FFFE 到 FFFF)和其他代理。

虽然 SCSU 压缩方式是可逆的,但它不是 UTF,因为依赖于不同的 SCSU 压缩器,同

样的字符串可以映射到多个不同的字节序上。

Q:关于编码形式可以在哪里获得更多的信息?

A:UTF 正式定义见 Unicode 标准 3.9 节:Unicode 编码形式。关于编码形式的更多

信息见 Unicode 技术报告 UTR#17:Unicode 字符编码模型。

Q:如何写一个 UTF 转换器?

Page 2: Utf bom faq

2 / 12

A:免费的开源项目 Unicode 国际组件(ICU)内置了一个。最新版本可以 在 ICU

项目的网站下载到。

Q:有哪些字节序不是由 UTF 生成的?如何表示它们?

A:所有的 UTF 都无法生成任意的字节序。例如,在 UTF-8 中每个形如 110xxxxx 的

字节序必须紧跟形如 10xxxxxx 的字节序。例如<110xxxxx0xxxxxxx>是非法的,不应

该被生成。当在转换或翻译时,遇到这个非法字节序,UTF-8 一致性处理“必须”

将第一个字节序 110xxxxx 当作终止错误:例如,要么发送错误信号,要么过滤掉

该字节序,或者要么用如 FFFD(REPLACEMENT CHARACTER)这样的标记符替代该

字节序。而后两者,它将继续处理第二个字节序 0xxxxxxx。

一致性处理“必须”不能将非法或病态的字节序翻译成字符,但是,它可能采取错

误恢复措施。任何一致性处理都不会使用不规则的字节序来编码带外数据。

Q:应该支持哪些 UTF 编码形式?

A:UTF-8 在 web 上普遍使用。UTF-16 在 Java 和 Windows 系统中使用。UTF-8 和

UTF-32 在很多 Linux 和 Unix 系统中使用。它们之间的转换是基于快速、无损的算

法的。这使得内部存储或处理仅使用一种专有的 UTF 编码,同时又支持多种编码

形式的数据的输入输出变得非常容易。

Q:各种 UTF 编码之间的区别是什么?

A:如下表格总结了各种 UTF 编码的一些属性。

名字 UTF-8 UTF-16 UTF-16BE UTF-16LE UTF-32 UTF-32BE UTF-32LE

最小编码

点 0000 0000 0000 0000 0000 0000 0000

最大编码

点 10FFFF 10FFFF 10FFFF 10FFFF 10FFFF 10FFFF 10FFFF

单位编码

大小 8 bits 16 bits 16 bits 16 bits 32 bits 32 bits 32 bits

字节序 N/A <BOM> big-endian little-endian <BOM> big-endian little-endian

每字符最

少字节数 1 2 2 2 4 4 4

每字符最

多字节数 4 4 4 4 4 4 4

Page 3: Utf bom faq

3 / 12

表中<BOM>表示字节序由一个字节序标记决定的。它在数据流的头部给出,否则为

big-endian。

Q:为什么有的 UTF 编码有 BE 或 LE 这样的标记,如 UTF-16LE?

A:UTF-16 使用 2 字节长度的编码单元,UTF-32 使用 4 字节长度的编码单元。对这

些 UTF 编码形式有 3 种风格:BE、LE 和无标记。BE 形式使用 big-endian 字节序(高

位字节在前),LE 形式使用 little-endian 字节序(地位字节在前),无标记形式默

认使用 big-endian 字节序,但是更多的情况是在头部包含一个字节序标记来指示实

际使用的字节序。

Q:有没有标准的方法来包装一个 Unicode 字符使其符合 8 位 ASCII 流吗?

A:4 个建议让 Unicode 符合 8 位格式。

a)使用 UTF-8。它保留了 ASCII 而非 Latin-1,因为字符>127 时和 Latin-1 不一致。

UTF-8 仅仅为 ASCII 字符使用 ASCII 中的字节。因此,在用 ASCII 字符作为语法字符

的任何重要环境中都表现不错,举例如,文件名、标记语言等等,但是所有其他字

符可能使用任意的字节。例如:“Latin 小字母 s 急性”(015B)会被编码为 2 字

节:C59B。

b)使用 Java 或 C 的转义风格:\uXXXXX 或\xXXXXX。这些格式在文本文件中是非标

准的,但在上面谈到的语言框架下有良好的定义,主要就是针对源文件。例如:波

兰语“wyjście”中间使用了“Latin 小字母 s 急性”(015B)编码后看起来是这样

的:“wyj\u015Bcie”。

c)在 HTML 或 XML 中使用&#xXXXX;或&#DDDDD;这样的数字字符转义风格。同样,

这些格式在文本文件中是非标准的,但在这些标记语言框架下有良好的定义。例如:

“wyjście”编码后看起来是这样的:“wyj&#x015B;cie”。

d)使用 SCSU。这种格式将 Unicode 压缩为 8 位格式,保留大多数 ASCII 字符,但

用一些控制码作为解码命令。尽管ASCII文本在 SCSU编码后看起来还是ASCII文本,

但是,有些字符可能偶尔会被编码为相同的字节值,它盲目地翻译任意字节值为

ASCII 字符,这使得 SCSU 不适合 8 位通道。例如:“<SC2> wyjÛ cie”中, <SC2> 表

示字节 0x12,“Û”对应的字节 0xDB。

Q:上述方法哪种最好?

A:这个问题依赖实际的环境:4 种方法中,d)使用空间最少,但不能在大多数 8

位环境中透明地使用。a)在文本文件中得到最广泛地支持。b)和 c)使用空间最

多,但在 Java 和 C 或者 HTML 和 XML 程序源文件中各自得到了广泛地支持。

Q:上述哪种格式最标准?

Page 4: Utf bom faq

4 / 12

A:所有 4 种方法都要求接收方能够理解这些格式,但 a)被认为是三个等效的

Unicode 编码形式之一,因此就是标准。在给定的上下文环境之外使用 b)或 c),

很明确是非标准的,但是它也是内部数据传输很好的解决方案。使用 SCSU 本身是

一种标准(针对压缩数据流),但很少有通用目的的接收方支持 SCSU,故它也只

是在内部数据传输中很有用而已。

UTF-8 的常见问题

Q:什么是 UTF-8?

A:UTF-8 是 Unicode 中面向字节的编码形式。详细的定义,见 Unicode 标准的 2.5

节“编码形式”和 3.9 节“Unicode 编码形式”。特别要看一下表 3-6 的 “UTF-8 位

分布”和表 3-7 的“良好编码的 UTF-8 字节序”,它们是编码形式的简明扼要的总

结。因为随着时间的推移 Unicode 技术委员会已经对 UTF-8 加固了定义,使得它成

为更加严格而唯一的序列并且禁止了某些非法字符的编码形式,故,确保你参考的

是最新版本的 Unicode 标准。关于 UTF-8 有个互联网 RFC3629。UTF-8 还在

ISO/IEC10646 的附录 D 中有定义。同时,请参见上面的常见问题:如何写一个 UTF

转换器?

Q:UTF-8 编码方案也如同基本处理器一样与是否是 little-endian 或 big-endian 是无关的

吗?

A:是的。既然 UTF-8 作为字节的序列来解释,那么它没有字节排列顺序的问题,

这些问题会在使用 16 位或 32 位编码单元中会出现。UTF-8 使用 BOM 只是为了标

记以区别于其他编码形式——与字节序本身无关。

Q:UTF-8 编码方案也如同基本系统一样与是否使用 ASCII 或 EBCDIC 编码是无关的吗?

A:UTF-8 只有一个定义。数据无论从基于 ASCII 或 EBCDIC 字符集转换而来,两者

之间都是严格一致的。然而,标准 UTF-8 的字节序与 EBCDIC 系统的互通性不好,

因为 ASCII 和 EBCDIC 的控制码布置是不一样的。Unicode 技术报告#16:UTF-EBCDIC

定义了一个特殊的 UTF,它能够与 EBCDIC 系统互通。

Q:如何将一个 UTF-16 成对的代理如<D800 DC00>转换为 UTF-8?是 1 个 4 字节序列还

是 2 个 3 字节序列?

A:UTF-8 的定义要求辅助字符(在 UTF-16 中使用成对的代理)按照单个 4 字节序

列编码。但是,在老式的软件中有一种广泛实践过的生成 3 字节序列对的方法,特

别是那些软件在 UTF-16 引入之前的时期,或者是与 UTF-16 有互通的在特殊限制的

环境下。这种编码和 UTF-8 的定义是不一致的。见 UTR #26:UTF-16 的 8 位兼容编

码方案(CESU),这是对非 UTF-8 数据格式的正式描述。使用 CESU-8 要特别小心,

数据不是因为格式相似而意外地被当作 UTF-8 的。

Page 5: Utf bom faq

5 / 12

Q:如何将 UTF-16 不成对的代理转换为 UTF-8?

如果在转换病态的 UTF-16 数据时遇到不成对的代理,会产生不一样的问题。通过

将这种自身不成对的代理表示为 3字节的序列,由此产生的UTF-8数据流是病态的。

当转换器忠实地反应输入的本质时,Unicode 一致性要求编码形式转换生成的数据

流总是合法的。因此,转换器“必须”将其作为错误对待。

UTF-16 的常见问题

Q:什么是 UTF-16?

A:UTF-16 使用单个 16 位编码单元来编码最常用的 63k 个 Unicode 字符,使用一

对 16 位编码单元(称作代理)来编码 1M 个不太常用的 Unicode 字符。

最初,Unicode 设计为一个单纯的 16 位编码,目的在于表示所有的现代稿件。(过

时的稿件被表示为私有使用的字符。)随着时间的推移,特别是在增加了 145000

复合字符来兼容遗留的字符集后,16 位显然是不够的。出于这种考虑产生了 UTF-16。

Q:什么是代理?

A:代理是在两个特殊的 Unicode 值的范围内编码点,它们保留作前导和拖尾使用,

构成了成对的 UTF-16 编码单元。前导,也称作高位,代理编码从 D800 到 DBFF。

拖尾,也称作低位,代理编码从 DC00 到 DFFF。因为它们不直接表示字符,所以称

为代理,但成对出现。

Q:从 UTF-16 转换为字符的算法是什么?

A:Unicode 标准曾经包含了一个简短的算法,现在只有一个位分布表。下面是三

个简短的代码片段,是由位分布表中的信息翻译成的 C 代码,它们现实与 UTF-16

的互相转换。

使用以下类型定义

typedef unsigned int16 UTF16;

typedef unsigned int32 UTF32;

第一代码片段从字符 C 计算高位(前导)代理。

const UTF16 HI_SURROGATE_START = 0xD800

UTF16 X = (UTF16) C;

UTF32 U = (C >> 16) & ((1 << 5) - 1);

UTF16 W = (UTF16) U - 1;

Page 6: Utf bom faq

6 / 12

UTF16 HiSurrogate = HI_SURROGATE_START | (W << 6) | X >> 10;

X、U 和 W 对应到表 3-4 UTF-16 位分布表的标签。下面的代码片段实现了低位代理。

const UTF16 LO_SURROGATE_START = 0xDC00

UTF16 X = (UTF16) C;

UTF16 LoSurrogate = (UTF16) (LO_SURROGATE_START | X & ((1 << 10) - 1));

最后,相反的情况,hi 和 lo 表示高位和低位代理,C 是产生的字符。

UTF32 X = (hi & ((1 << 6) -1)) << 10 | lo & ((1 << 10) -1);

UTF32 W = (hi >> 6) & ((1 << 5) - 1);

UTF32 U = W + 1;

UTF32 C = U << 16 | X;

还需要一个函数来保证 C、hi 和 lo 在适当的范围内。

Q:难道没有简单点的方法吗?

A:有一个不跟随位分布表的更简单的算法。

// 常量

const UTF32 LEAD_OFFSET = 0xD800 - (0x10000 >> 10);

const UTF32 SURROGATE_OFFSET = 0x10000 - (0xD800 << 10) - 0xDC00;

// 计算

UTF16 lead = LEAD_OFFSET + (codepoint >> 10);

UTF16 trail = 0xDC00 + (codepoint & 0x3FF);

UTF32 codepoint = (lead << 10) + trail + SURROGATE_OFFSET;

Q:为什么有些人反对 UTF-16?

A:人们熟悉的可变宽度的东亚字符集,如为 Shift - JIS(SJIS),有时需要两个编码

单元表示单个字符,可以理解这让人对 UTF-16 很担心。他们非常熟悉可变宽度编

码造成的问题。但是,UTF-16 与 SJIS 中使用的机制有一些很重要的不同:

重叠:

在 SJIS 中,前导与拖尾编码单元值之间是有重叠的,拖尾与单个编码单元值之

间也是。这导致了一些问题:

匹配错误。例如,在拖尾编码单元中查找“a”可能是一个日本文字符。

失去了随机访问的高效。为了知道是否在字符范围内,不得不后向搜索找到

边界。

Page 7: Utf bom faq

7 / 12

让文本非常脆弱。假如一个单元从前导-拖尾编码单元中丢失,紧随其后的

很多字符也随之损坏。

在 UTF-16 中,高位和低位代理编码点范围和单个编码单元一样都是完全不相交

的。下面的问题都不会产生:

匹配错误。There are no false matches.

从每个编码单元值中,字符边界位置能直接确定。

丢失代理编码仅导致单个字符损坏。

频率:

绝大多数 SJIS 字符需要 2 个编码单元,但是单个编码单元的字符非常常见并尤

其重要,例如文件名。

用 UTF-16,相对较少的字符需要 2 个编码单元。绝大多数常见的字符是单个编

码单元的。甚至在东亚字文字中,病态代理对几率应该在文本存储平均值的 1%

以下。当然,某些文档可能病态代理对的几率较高,就像“phthisique ”尽管在

英文中的频率不高,但在特殊的学术文章中经常出现的一样。

Q:UTF-16 会扩展到超过一百万的字符吗?

A:不会。Unicode 和 ISO10646 都有策略来正式限制未来分配的编码在 UTF-16 能表

示的整数范围内(0 到 1114111)。甚至如果其他编码形式(例如,其他 UTF)能

表示更大的整数,这些策略意味着所有的编码形式总是表示相同的字符集。超过百

万的编码远远大于满足容纳 Unicode 编码字符的目标,当然不是针对字形。Unicode

不是设计用来编码任意数据的。例如,想要将“整个历史上在纸上出现过的所有字

符实例”都要赋予它自己单独的编码,那恐怕需要万亿或百万亿计的编码;这种努

力是高尚,但是可能不会有人使用这样的 Unicode 编码。

Q:哪些 16 位值是非法的?

A:FFFE、FFFF 和从 FDD0 到 FDEF 的 32 个值(译注:共 34 个)表示非字符。在数

据交换时它们是非法的,但可以在一中实现在内部自由使用。不成对的代理也是非

法的,任何 D800 到 DBFF 范围的值后面没有紧跟 DC00 到 DFFF 范围的值。

Q:哪些不成对的代理是非法的?

A:一些编码点是设计为非字符。在数据交换时它们是非法的,但可以在一中实现

在内部自由使用。有 32 个非字符是辅助字符,相应成对的代理如下表所示。

UTF-16 UTF-8 UCS-4

D83F DFF* F0 9F BF B* 0001FFF*

D87F DFF* F0 AF BF B* 0002FFF*

D8BF DFF* F0 BF BF B* 0003FFF*

Page 8: Utf bom faq

8 / 12

D8FF DFF* F1 8F BF B* 0004FFF*

D93F DFF* F1 9F BF B* 0005FFF*

D97F DFF* F1 AF BF B* 0006FFF*

...

DBBF DFF* F3 BF BF B* 000FFFF*

DBFF DFF* F4 8F BF B* 0010FFF*

* = E or F

指向未分配的字符成对的代理不应该出现在你生成的数据中,而从一个与最新版本

的 Unicode 标准一致的系统中的生成的数据是可能合法地出现的。

Q:因为辅助字符不常见,是否意味着可以忽略它们?

A:只是由于辅助字符(在 UTF-16 中表示为成对的代理)不常见,而不是意味着它

们可以被忽视。它们包含:

和日本移动电话互通的绘图文字和表情符号

不常见(但不是不用)的 CJK 字符,对于人名和地址很重要

表意文字变化序列的变化选择器

重要的数学符号

数量众多的少数名族和历史性的稿件,对有些用户群体很重要

Q:在代码中如何处理辅助字符?

A:和 BMP 字符相比,在大多数上下文中辅助字符相对不常见。当要为了最佳性能

而优化实现时,这个事实可以考虑进去:执行速度、内存用量和存储空间。对 UTF-16

的实现来说这特别有用,在 UTF-8 的实现中程度较轻。

UTF-32 的常见问题

Q:什么是 UTF-32?

A:在 UTF-32 中,任何 Unicode 字符都可以表示为单个 32 位编码单元。该 4 字节

的编码单元对应于 Unicode 标量值,它是一个关联到 Unicode 字符的抽象数字。

UTF-32 是 ISO10646 编码机制的子集,称作 UCS-4。详细的信息参见 Unicode 标准

的 3.9 节:“Unicode 编码形式”。

Q:在内存中 Unicode 字符串排序应该使用 UTF-32(UCS-4)吗?

Page 9: Utf bom faq

9 / 12

A:看情况。如果需要经常访问的 API 字符串参数要求 UTF-32 形式,那始终使用

UTF-32 字符串比较方便。但缺点就是迫使每个字符都是 32 位的,事实上只需要 21

位。在普通的文本中平均使用高位数字的字符很少,这使得有效比例非常低。在很

多情况下这无所谓,每个字符拥有固定数量的编码单元带来的便利起到了决定性的

作用。

同样数量的字符却增加了不少存储空间确实对应用程序处理大容量的文本数据时

一种浪费:这意味着更早到达缓存上限;明显增加读/写次数或达到带宽上限;需

要更多的存储空间。有一些实现是这样做的:字符串使用UTF-16,单个字符是UTF-32,

下面有个例子。

Unicode 的主要卖点就是提供了表示世界上所有字符的方法,消除了使用各种字符

集的需要,并避免了相关数据损坏的问题。这些特性足矣让整个行业都趋于使用

Unicode(UTF-16)。尽管 UTF-32 的表示确实让编程模型有一定程度上的简化,平

均存储空间的增加也确实个缺点,全部转换成 UTF-32 并非引人注目。

Q:在 API 中使用 UTF-32 的接口会如何?

A:除了有些环境以 UTF-32 在内存中存储文本,大多数 Unicode API 使用 UTF-16。

使用 UTF-16 的 API,底层索引是在存储或编码单元级别的。对于字形或单词是在更

高的级别机制中在编码单元上指定了它们的边界。这提供了底层的效率,并保证了

高层的功能。

注:此处有删节,请参看英文原稿。

Q:只有 UTF-16 的 string API 而没有 UTF-32 的 char API 是否会导致一些问题?

A:几乎所有的国际化函数(upper-,lower-,标题,大小写,绘图,度量,集合,

音译,grapheme-,word-,断行等等)在 API 中使用字符串参数而不是单个编码点

(UTF-32)。单个编码点 API 几乎总是导致错误,除了非常简单的语言以外。因为,

为了得到正确的答案,上下文总是很复杂,或者为了返回正确的答案,需要将多个

字符生成一个序列,或者两者都有。

注:此处有删节,请参看英文原稿。

Q:API 中只使用字符串参数的规则是否有例外?

A:最大的例外是例如获得字符属性(也就是说 UCD 中通用分类或规范类)这种底

层操作。对于这些,有方便的接口与 UTF-16 和 UTF-32 之间互相转换,也允许遍历

字符串并返回 UTF-32 的值(即使它的内部格式是 UTF-16 的)。

Q:如何将 UTF-16 成对的代理如<D800 DC00>转换到 UTF-32 中?是 1 个 4 字节序列还是

2 个 4 字节的序列?

Page 10: Utf bom faq

10 / 12

A:UTF-32 的定义要求辅助字符(在 UTF-16 中使用成对的代理)应该编码成单个 4

字节的序列。

Q:如何将 UTF-16 不成对的代理转换到 UTF-32 中?

A:如果在转换病态的 UTF-16 数据时碰到不成对的代理,任何一致性转换器必须视

之为错误。如若将这些不成对的代理表示为 UTF-32 的数据,它们是病态的。虽然

它忠实地反映输入的本质,Unicode 的一致性要求编码形式转换始终生成有效的数

据流结果。

BOM 的常见问题

Q:什么是 BOM?

A:字节序标记(BOM)是在数据流的开头包含 U+FEFF 的编码,那里可以用来定

义字节序和编码形式,主要是常规的明文文件。在一些高级协议中,会明确定义在

Unicode 数据流中使用 BOM 是强制的(或是禁止的)。

Q:BOM 在哪里非常有用?

A:在输出为文本的文件的开头使用 BOM,它指示文件是 big-endian或是 little-endian

——它还能指示文件是 Unicode,因为它不在传统的编码中,而是可以作为一个使

用特定编码形式的签名。

Q:“endian”是什么意思?

A:计算机内存中可以存储长度大于 1 字节的数据,只是选择高位(MSB)在前还

是在后的问题。前者称作 big-endian,后者称作 little-endian。进行数据交换时,在

发送端系统中的字节看似顺序“正确”也许在接收端系统中顺序“错乱”。在这种

情况下,BOM 看上去像 0xFFFE 的非字符,使得接收系统在处理数据之前先应用字

节逆转。UTF-8 是面向字节的,因此没有这个问题,然而,开头的 BOM 有助于鉴

别数据流是否是 UTF-8 的。

Q:是否只在 16 位 Unicode 文本中使用 BOM?

A:不是。BOM 用作签名使用,无论 Unicode 文本采用什么编码形式:UTF-16、UTF-8

或 UTF-32。用某种格式转换 Unicode 字符 U+FEFF 得到了一些确定的字节,这就是

BOM。以那种形式,BOM 不仅指示了文件是 Unicode,而且表示了特定的格式。例

如:

数据 编码形式

Page 11: Utf bom faq

11 / 12

00 00 FE FF UTF-32, big-endian

FF FE 00 00 UTF-32, little-endian

FE FF UTF-16, big-endian

FF FE UTF-16, little-endian

EF BB BF UTF-8

Q:UTF-8 数据流可以包含 BOM(以 UTF-8 形式)吗?如果是,是否仍然可以认为其他

UTF-8 字节是 big-endian 字节序的呢?

A:是的,UTF-8 可以包含 BOM。但是,不同的字节序系统对字节流没有影响。UTF-8

总是有相同的字节顺序。开头的 BOM 只是用来签名——指示被标记的文件是 UTF-8。

注意,有些 UTF-8 解码器不希望接收 BOM。若在 8 位环境中要透明地使用 UTF-8,

使用 BOM 会干扰那些格式期望在开头处有特定的 ASCII 字符的协议或文件,例如

在 Unix 的 shell 脚本在文件开头使用“#!”。

Q:如何处理出现在文件中间的 U+FEFF?

A:在缺少协议支持,未在文本流的开头使用 BOM 时,U+FEFF 不应该出现。为了

向后兼容,它应该被看作 ZERO WIDTH NON-BREAKING SPACE (ZWNBSP)(译注:

零宽度非阻断空格),它是文件内容或字符串的一部分。强烈推荐在 ZWNBSP 后使

用 U+2060 WORD JOINER 作为单词连接语意,因为它和 BOM 之间不会混淆。在设

计一门标记语言或数据协议时,使用 U+FEFF 限制为字节序标记。这种情况下,任

何在文件中间出现的 U+FEFF 被看作是不支持的字符。

Q:正在使用文件开头有 BOM 的协议,那么如何表示一个 ZWNBSP?

A:使用 U+2060(WORD JOINER)作为代替。

Q:U+FEFF 如何标记数据而不将 U+FEFF 翻译成 BOM?

A:使用标签 UTF-16BE 指示 big-endian 的 UTF-16 文本,UTF-16LE 指示 little-endian

的 UTF-16 文本。如果使用 BOM 就简单地使用 UTF-16 标记文本。

Q:为什么不一直使用需要 BOM 的协议?

A:在数据有关联类型的地方,如数据库的字段,BOM 是没有必要的。特别地,如

果文本数据流标记为 UTF-16BE、UTF-16LE、UTF-32BE 或者 UTF-32LE,BOM 既不需

要也不允许。任何 U+FEFF 都会被翻译为 ZWNBSP 。

Page 12: Utf bom faq

12 / 12

不要将数据库或字段集合中的每个字符串都用 BOM 来标记,因为这既浪费空间,

又让字符串连接变得更加复杂。其次,这意味着两个数据字段可能有一样的内容但

在二进制下不相等(可能其一是由 BOM 开始的)。

Q:应该如何处理 BOM?

A:以下有几条建议:

1. 一个特定的协议(例如,微软惯用的.txt 文件)可能要求在数据流上(如文件),

使用 BOM。当需要去遵循这样的协议时使用 BOM。

2. 有些协议在未标记文本的情况下允许 BOM 是可选的。这些情况,

已经知道文本数据流是纯文本,但不知道编码形式,BOM 可以用作签名。如

无 BOM,编码形式可能是任意的。

已经知道文本数据流是纯 Unicode 文本,但不知道字节序,BOM 可以用作签

名。如无 BOM,文本应该被看作 big-endian。

3. 有些面向字节的协议期望在文件开头是 ASCII 字符。如果这些协议使用 UTF-8,那

就应该避免使用 BOM 作为编码形式的签名。

4. 在知道数据流的精确类型的地方(例如,Unicode 的 big-endian 或 little-endian),

不应该使用 BOM。特别地,无论何时数据流申明为 UTF-16BE、UTF-16LE、UTF-32BE

或者 UTF-32LE,禁止使用 BOM。 (参看 Q:UCS-2 与 UTF-16 之间的区别是什么?)