编码学习整理(四)—— UTF-8

 

UTF-8(8 位元 Universal Character Set/Unicode Transformation Format)是一种针对 Unicode 的可变长度字符编码。它可以用来表示 Unicode 标准中的任何字符,且其编码中的第一个字节仍与 ASCII 相容,这使得原来处理 ASCII 字符的软件无须或只须做少部份修改,即可继续使用。因此,它逐渐成为电子邮件网页及其他储存或传送文字的应用中,优先采用的编码。

UTF-8 使用一至四个字节为每个字符编码:

  1. 128 个 US-ASCII 字符只需一个字节编码(Unicode 范围由 U+0000 至 U+007F)。
  2. 带有附加符号拉丁文希腊文西里尔字母亚美尼亚语希伯来文阿拉伯文叙利亚文它拿字母则需要二个字节编码(Unicode 范围由 U+0080 至 U+07FF)。
  3. 其他基本多文种平面(BMP)中的字符(这包含了大部分常用字)使用三个字节编码。
  4. 其他极少使用的 Unicode 辅助平面的字符使用四字节编码。

对上述提及的第四种字符而言,UTF-8 使用四个字节来编码似乎太耗费资源了。但 UTF-8 对所有常用的字符都可以用三个字节表示,而且它的另一种选择,UTF-16编码,对前述的第四种字符同样需要四个字节来编码,所以要决定 UTF-8 或 UTF-16 哪种编码比较有效率,还要视所使用的字符的分布范围而定。不过,如果使用一些传统的压缩系统,比如 DEFLATE,则这些不同编码系统间的的差异就变得微不足道了。若顾及传统压缩算法在压缩较短文字上的效果不大,可以考虑使用 Standard Compression Scheme for Unicode(SCSU)。

互联网工程工作小组(IETF)要求所有互联网协议都必须支援 UTF-8 编码。[1] 互联网邮件联盟(IMC)建议所有电子邮件软件都支援 UTF-8编码。所有主要的电子邮件软件中,只有 Eudora 不支援 UTF-8 编码。[1]

 

 

 

历史

1992年初,为建立良好的字节串编码系统(byte-stream encoding)以供多字节字符集(multi-byte character sets)使用,开始了一个正式的研究。ISO/IEC 10646的初稿中有一个非必须的附录,名为UTF。当中包含了一个供32位元字符使用的字节串编码系统。这个编码方式的性能并不令人满意,但它提出了将0-127的范围保留给ASCII以相容旧系统的概念。

1992年7月,X/Open委员会XoJIG开始寻求一个较佳的编码系统。Unix系统实验室(UNIX System Laboratories, USL)的Dave Prosser为此提出了一个编码系统的建议。它具备可更快速实作的特性,并引入一项新的改进。其中,7位元ASCII符号只代表原来的意思,所有多字节序列则会包含第8位元的符号,也就是所谓的最高有效位元(Most significant bit)。

1992年8月,这个建议由IBMX/Open的代表流传到一些感兴趣的团体。与此同时,贝尔实验室Plan 9操作系统工作小组的肯·汤普逊对这编码系统作出重大的修改,让编码可以自我同步(self-synchronizing),使得不必从字串的开首读取,也能找出字符间的分界。1992年9月2日,肯·汤普逊Rob Pike一起在美国新泽西州一架餐车的餐桌垫上描绘出此设计的要点。接下来的日子,Pike及汤普逊将它实现,并将这编码系统完全应用在Plan 9当中,及后他将有关成果回馈X/Open。

1993年1月25-29日的在圣地牙哥举行的USENIX会议首次正式介绍UTF-8。

自1996年起,微软CAB(MS Cabinet)规格在UTF-8标准正式落实前就明确容许在任何地方使用UTF-8编码系统。但有关的编码器实际上从来没有实作这方面的规格。

描述

目前有好几份关于UTF-8详细规格的文件,但这些文件在定义上有些许的不同:

  • RFC 3629 / STD 63(2003),这份文件制定了UTF-8是标准的互联网协议元素
  • 第四版,The Unicode Standard,§3.9-§3.10(2003)
  • ISO/IEC 10646-1:2000附加文件D(2000)

它们取代了以下那些被淘汰的定义:

  • ISO/IEC 10646-1:1993修正案2/附加文件R(1996)
  • 第二版,The Unicode Standard,附录A(1996)
  • RFC 2044(1996)
  • RFC 2279(1998)
  • 第三版,The Unicode Standard,§2.3(2000)及勘误表#1:UTF-8 Shortest Form(2000)
  • Unicode Standard 附加文件#27: Unicode 3.1(2001)

事实上,所有定义的基本原理都是相同的,它们之间最主要的不同是支援的字符范围及无效输入的处理方法。

Unicode字符位元被分割为数个部分,并分配到UTF-8的字节串中较低的位元的位置。在U+0080的以下字符都使用内含其字符的单字节编码。这些编码正好对应7位元的ASCII字符。在其他情况,有可能需要多达4个字符组来表示一个字符。这些多字节的最高有效位元会设定成1,以防止与7位元的ASCII字符混淆,并保持标准的字节主导字串(standard byte-oriented string)运作顺利。

代码范围
十六进制
标量值(scalar value)
二进制
UTF-8
二进制十六进制
注释
000000 - 00007F
128个代码
00000000 00000000 0zzzzzzz0zzzzzzz(00-7F)ASCII字符范围,字节由零开始
七个z七个z
000080 - 0007FF
1920个代码
00000000 00000yyy yyzzzzzz110yyyyy(C2-DF) 10zzzzzz(80-BF)第一个字节由110开始,接着的字节由10开始
三个y;二个y;六个z五个y;六个z
000800 - 00D7FF
00E000 - 00FFFF
61440个代码 [Note 1]
00000000 xxxxyyyy yyzzzzzz1110xxxx(E0-EF) 10yyyyyy 10zzzzzz第一个字节由1110开始,接着的字节由10开始
四个x;四个y;二个y;六个z四个x;六个y;六个z
010000 - 10FFFF
1048576个代码
000wwwxx xxxxyyyy yyzzzzzz11110www(F0-F4) 10xxxxxx 10yyyyyy 10zzzzzz由11110开始,接着的字节由10开始
三个w;二个x;四个x;四个y;二个y;六个z三个w;六个x;六个y;六个z
Note 1  Unicode在范围D800-DFFF中不存在任何字符, 基本多文种平面中约定了这个范围用于UTF-16扩展标识 辅助平面(两个UTF-16表示一个 辅助平面字符)。当然,任何编码都是可以被转换到这个范围,但在unicode中他们并不代表任何合法的值。

例如,希伯来语字母 aleph(א)的Unicode代码是 U+05D0,按照以下方法改成 UTF-8:

  • 它属于 U+0080到U+07FF区域,这个表说明它使用双字节,110yyyyy 10zzzzzz.
  • 十六进制 的 0x05D0换算成二进制就是 101-1101-0000.
  • 这11位数按顺序放入"y"部分和"z"部分:11010111 10010000.
  • 最后结果就是双字节,用十六进制写起来就是 0xD7 0x90,这就是这个字符aleph(א)的UTF-8编码。

所以开始的128个字符(US-ASCII)只需一字节,接下来的1920个字符需要双字节编码,包括带附加符号拉丁字母希腊字母西里尔字母科普特语字母,亚美尼亚语字母,希伯来文字母和阿拉伯字母的字符。基本多文种平面中其余的字符使用三个字节,剩余字符使用四个字节。

根据这种方式可以处理更大数量的字符。原来的规范允许长达6字节的序列,可以覆盖到31位元(通用字符集原来的极限)。尽管如此,2003年11月UTF-8 被 RFC 3629 重新规范,只能使用原来Unicode定义的区域, U+0000到U+10FFFF。根据这些规范,以下字节值将无法出现在合法 UTF-8序列中:

编码(二进制编码(十六进制注释
1100000xC0, C1过长编码:双字节序列的头字节,但码点<= 127
1111111xFE, FF无法达到:7 或8字节序列的头字节
111110xx
1111110x
F8, F9, FA, FB, FC, FDRFC 3629规范:5 或 6字节序列的头字节
11110101
1111011x
F5, F6, F7RFC 3629规范:码点超过10FFFF的头字节

UTF-8的衍生物

Windows

虽然不是标准,但许多Windows 程序(包括Windows 笔记本)在UTF-8编码的档案的开首加入一段字节串EF BB BF。这是字节顺序记号 U+FEFF 的 UTF-8 编码结果。对于没有预期要处理UTF-8的文字编辑器和浏览器会显示成 ISO-8859-1 字符串 ""。

Java

在通常用法下,Java程序语言在通过InputStreamReaderOutputStreamWriter读取和写入串的时候支持标准UTF-8。但是,Java也支持一种非标准的变体UTF-8,供对象的系列化Java本地界面和在class文件中的嵌入常数时使用的modified UTF-8

变种 UTF-8

标准和变种的UTF-8有两个不同点。第一,空字符(null character,U+0000)使用双字节的 0xc0 0x80,而不是单字节的 0x00。这保证了在已编码字串中没有嵌入空字节。因为C语言等语言程序中,单字节空字符是用来标志字串结尾的。当已编码字串放到这样的语言中处理,一个嵌入的空字符将把字串一刀两断。

第二个不同点是基本多文种平面之外字符的编码的方法。在标准UTF-8中,这些字符使用4字节形式编码,而在改正的UTF-8中,这些字符和UTF-16一样首先表示为代理对(surrogate pairs),然后再像CESU-8那 样按照代理对分别编码。这样改正的原因更是微妙。Java中的字符为16位长,因此一些Unicode字符需要两个Java字符来表示。语言的这个性质盖 过了Unicode的增补平面的要求。尽管如此,为了要保持良好的向后兼容、要改变也不容易了。这个改正的编码系统保证了一个已编码字串可以一次编为一个 UTF-16码,而不是一次一个Unicode码点。 不幸的是,这也意味着UTF-8中需要4字节的字符在变种UTF-8中变成需要6字节。

因为变种UTF-8并 不是 UTF-8,所以用户在交换信息和使用互联网的时候需要特别注意不要误把变种UTF-8当成UTF-8数据。

Mac OS X

Mac OS X操作系统使用正式分解万国码(canonically decomposed Unicode),在文件系统中使用UTF-8编码进行文件命名,这做法通常被称为UTF-8-MAC。正式分解万国码中,预分解字符是被禁止使用的,必须以组合字符取代。

这种方法使分类变得非常简单,但是会搞混那些使用预分解字符为标准、组合字符用来显示特殊字符的软件。Mac系统的这种NFD数据是万国码规范化(Unicode normalization)的一种格式。而其他系统,包括WindowsLinux,使用万国码规范的NFC形式,也是W3C标准使用的形式。所以NFD数据必须典型的转换成NFC才能被其他平台或者网络使用。

在此有关于此问题的讨论 Apple Q&A 1173

设计UTF-8的理由

UTF-8的设计有以下的多字符组序列的特质:

  • 单字节字符的最高有效位元永远为0。
  • 多字节序列中的首个字符组的几个最高有效位元决定了序列的长度。最高有效位为110的是2字节序列,而1110的是三字节序列,如此类推。
  • 多字节序列中其余的字节中的首两个最高有效位元为10

UTF-8的这些特质,保证了一个字符的字节序列不会包含在另一个字符的字节序列中。这确保了以字节为基础的部份字串比对(sub-string match)方法可以适用于在文字中搜寻字或词。有些比较旧的可变长度8位元编码(如Shift JIS)没有这个特质,故字串比对的算法变得相当复杂。虽然这增加了UTF-8编码的字串的信息冗余,但是利多于弊。另外,资料压缩并非Unicode 的目的,所以不可混为一谈。即使在传送过程中有部份字节因错误或干扰而完全遗失,还是有可能在下一个字符的起点重新同步,令受损范围受到限制。

另一方面,由于其字节序列设计,如果一个疑似为字符串的序列被验证为UTF-8编码,那么我们可以有把握地说它是UTF-8字符串。一段两字节随机 序列碰巧为合法的UTF-8而非ASCII 的机率为32分1。对于三字节序列的机率为256分3,对更长的序列的机率就更低了。

过长的资料排列(overlong forms)、输入无效及保安的考虑

优点及缺点

关于字符串长度的一个注解:

总体来说,在Unicode字符串中不可能由码点数量决定显示它所需要的长度,或者显示字符串之后在文本缓冲区中光标应该放置的位置;组合字符、变宽字体、不可打印字符和从右至左的文字都是其归因。

所以尽管在UTF-8字符串中字符数量与码点数量的关系比UTF-32更为复杂,在实际中很少会遇到有不同的情形。

  • 总体
    • 优点
      • UTF-8是ASCII的一个超集。因为一个纯ASCII字符串也是一个合法的UTF-8字符串,所以现存的ASCII文本不需要转换。为传统的扩展ASCII字符集设计的软件通常可以不经修改或很少修改就能与UTF-8一起使用。
      • 使用标准的面向字节的排序例程对UTF-8排序将产生与基于Unicode代码点排序相同的结果。(尽管这只有有限的有用性,因为在任何特定语言或文化下都不太可能有仍可接受的文字排列顺序。)
      • UTF-8和UTF-16都是可扩展标记语言文档的标准编码。所有其它编码都必须通过显式或文本声明来指定。[2]
      • 任何面向字节字符串搜索算法都可以用于UTF-8的数据(只要输入仅由完整的UTF-8字符组成)。但是,对于包含字符记数的正则表达式或其它结构必须小心。
      • UTF-8字符串可以由一个简单的算法可靠地识别出来。就是,一个字符串在任何其它编码中表现为合法的UTF-8的可能性很低,并随字符串长度增 长而减小。举例说,字符值C0,C1,F5至FF从来没有出现。为了更好的可靠性,可以使用正则表达式来统计非法过长和替代值(可以查看W3 FAQ: Multilingual Forms上的验证UTF-8字符串的正则表达式)。
    • 缺点
      • 一份写得很差(并且与当前标准的版本不兼容)的UTF-8解析器可能会接受一些不同的伪UTF-8表示并将它们转换到相同的Unicode输出上。这为设计用于处理八位表示的校验例程提供了一种遗漏信息的方式。

使用UTF-8的原因

ASCII转换成UCS-2,在编码前插入一个0x0。用这些编码,会含括一些控制符,比如 " 或 '/',这在UNIX和一些C函数中,将会产生严重错误。因此可以肯定,UCS-2不适合作为Unicode的外部编码,也因此诞生了UTF-8。

UTF-8的编码方式

UTF-8是UNICODE的一种变长度的编码表达方式 《一般UNICODE为双字节(指UCS2)》,它由Ken Thompson于1992年建立,现在已经标准化为RFC 3629。UTF-8就是以8位为单元对UCS进行编码,而UTF-8不使用大尾序和小尾序的形式,每个使用UTF-8储存的字符,除了第一个字节外,其余字节的头两个位元都是以 "10" 开始,使文字处理器能够较快地找出每个字符的开始位置。

但为了与以前的ASCII码相容(ASCII为一个字节),因此 UTF-8 选择了使用可变长度字节来储存 Unicode:

Unicode和UTF-8之间的转换关系表
UCS-4编码UTF-8字节流
U-00000000 – U-0000007F:0xxxxxxx
U-00000080 – U-000007FF:110xxxxx 10xxxxxx
U-00000800 – U-0000FFFF:1110xxxx 10xxxxxx 10xxxxxx
U-00010000 – U-001FFFFF:11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
U-00200000 – U-03FFFFFF:111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
U-04000000 – U-7FFFFFFF:1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
  • 在ASCII码的范围,用一个字节表示,超出ASCII码的范围就用字节表示,这就形成了我们上面看到的UTF-8的表示方法,这様的好处是当 UNICODE文件中只有ASCII码时,储存的文件都为一个字节,所以就是普通的ASCII文件无异,读取的时候也是如此,所以能与以前的ASCII文 件相容。
  • 大于ASCII码的,就会由上面的第一字节的前几位表示该unicode字符的长度,比如110xxxxxx前三位的二进制表示告诉我们这是个 2BYTE的UNICODE字符;1110xxxx是个三位的UNICODE字符,依此类推;xxx 的位置由字符编码数的二进制表示的位填入。越靠右的 x 具有越少的特殊意义。只用最短的那个足够表达一个字符编码数的多字节串。注意在多字节串中,第一个字节的开头"1"的数目就是整个串中字节的数目。。

ASCII字母继续使用1字节储存,重音文字希腊字母西里尔字母等使用2字节来储存,而常用的汉字就要使用3字节。辅助平面字符则使用4字节。

在UTF-8文件的开首,很多时都放置一个U+FEFF字符(UTF-8 以 EF,BB,BF 代表),以显示这个文字档案是以UTF-8编码。

UTF-8的特性

UTF-8图表说明
 UTF-8
Smallest code point0000
Largest code point10FFFF
Code unit size8 bits
Byte orderN/A
Minimal bytes/character1
Maximal bytes/character4
  • UCS 字符 U+0000 到 U+007F (ASCII) 被编码为字节 0x00 到 0x7F(ASCII 兼容),这也意味着只包含 7 位 ASCII 字符的文件在 ASCII 和 UTF-8 两种编码方式下是一样的。
  • 所有 >U+007F 的 UCS 字符被编码为一个多个字节的串,每个字节都有标记位集。因此,ASCII 字节 (0x00-0x7F) 不可能作为任何其他字符的一部分。
  • 表示非 ASCII 字符的多字节串的第一个字节总是在 0xC0 到 0xFD 的范围里,并指出这个字符包含多少个字节。多字节串的其余字节都在 0x80 到 0xBF 范围里,这使得重新同步非常容易,并使编码无国界,且很少受丢失字节的影响。
  • 可以编入所有可能的 231个 UCS 代码
  • UTF-8 编码字符理论上可以最多到 6 个字节长,然而 16 位 BMP 字符最多只用到 3 字节长。
  • Bigendian UCS-4 字节串的排列顺序是预定的。
  • 字节 0xFE 和 0xFF 在 UTF-8 编码中从未用到,同时,UTF-8以字节为编码单元,它的字节顺序在所有系统中都是一様的,没有字节序的问题,也因此它实际上并不需要BOM
  • 与 UTF-16 或其他 Unicode 编码相比,对于不支援 Unicode 和 XML 的系统,UTF-8 更不容易造成问题。

【注】

  • UTF为UCS / Unicode Transformation Format“Unicode转换格式”的缩写。
  • UCS的中文全称为:信息技术--通用多八位编码字符集 (Universal Multi-octet Coded Character Set),由ISO/IEC 10646 标准描述。

UTF-8编码的缺点

不利于正则表达式检索

正则表达式可以进行很多英文高级的模糊检索。例如,[a-h]表示a到h间所有字母。

同样GBK编码的中文也可以这样利用正则表达式,比如在只知道一个字的读音而不知道怎么写的情况下,也可用正则表达式检索,因为GBK编码是按读音排序的。只是UTF-8不是按读音排序的,所以会对正则表达式检索造成不利影响。但是这种使用方式并未考虑中文中的破音字,因此影响不大。Unicode是按部首排序的,因此在只知道一个字的部首而不知道如何发音的情况下,UTF-8 可用正则表达式检索而GBK不行。

其他

与其他 Unicode 编码相比,特别是UTF-16,在 UTF-8 中 ASCII 字符占用的空间只有一半,可是在一些字符的 UTF-8 编码占用的空间就要多出,特别是中文、日文和韩文(CJK)这样的象形文字,所以具体因素因文档而异,但不论哪种情况,差别都不可能很明显。

注释

  1. ^ 参考RFC 2277 section 3.1

参考

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值