UTF-8(8-bit Unicode Transformation Format)是一种针对Unicode的可变长度字符编码,也是一种前缀码。它可以用一至四个字节对Unicode字符集中的所有有效编码点进行编码,属于Unicode标准的一部分,最初由肯·汤普逊和罗布·派克提出。[2][3]由于较小值的编码点一般使用频率较高,直接使用Unicode编码效率低下,大量浪费内存空间。UTF-8就是为了解决向后兼容ASCII码而设计,Unicode中前128个字符,使用与ASCII码相同的二进制值的单个字节进行编码,而且字面与ASCII码的字面一一对应,这使得原来处理ASCII字符的软件无须或只须做少部分修改,即可继续使用。因此,它逐渐成为电邮、网页及其他存储或发送文字优先采用的编码方式。
此条目需要补充更多来源。 (2018年12月27日) |
此条目翻译品质不佳。 |
自2009年以来,UTF-8一直是万维网的最主要的编码形式(对所有,而不仅是Unicode范围内的编码)(并由WHATWG宣布为强制性的“适用于所有事物(for all things)”,[4]截止到2019年11月, 在所有网页中,UTF-8编码应用率高达94.3%(其中一些仅是ASCII编码,因为它是UTF-8的子集),而在排名最高的1000个网页中占96%。[5] 第二热门的多字节编码方式Shift JIS和GB 2312分别具有0.3%和0.2%的占有率。[6][7][1]Internet邮件联盟( Internet Mail Consortium, IMC)建议所有电邮程序都能够使用UTF-8展示和创建邮件,[8] W3C建议UTF-8作为XML文件和HTML文件的默认编码方式。[9]互联网工程工作小组(IETF)要求所有互联网协议都必须支持UTF-8编码[10]。互联网邮件联盟(IMC)建议所有电邮软件都支持UTF-8编码。[11]
历史
1992年初,为建立良好的字节串编码系统以供多字节字符集使用,开始了一个正式的研究。ISO/IEC 10646的初稿中有一个非必须的附录,名为UTF。当中包含了一个供32位元的字符使用的字节串编码系统。这个编码方式的性能并不令人满意,但它提出了将0-127的范围保留给ASCII以兼容旧系统的概念。
1992年7月,X/Open委员会XoJIG开始寻求一个较佳的编码系统。Unix系统实验室(USL)的Dave Prosser为此提出了一个编码系统的建议。它具备可更快速实现的特性,并引入一项新的改进。其中,7位元的ASCII符号只代表原来的意思,所有多字节序列则会包含第8位元的符号,也就是所谓的最高有效位元。
1992年8月,这个建议由IBMX/Open的代表流传到一些感兴趣的团体。与此同时,贝尔实验室九号项目操作系统工作小组的肯·汤普逊对这编码系统作出重大的修改,让编码可以自我同步,使得不必从字符串的开首读取,也能找出字符间的分界。1992年9月2日,肯·汤普逊和罗勃·派克一起在美国新泽西州一架餐车的餐桌垫上描绘出此设计的要点。接下来的日子,Pike及汤普逊将它实现,并将这编码系统完全应用在九号项目当中,及后他将有关成果反馈X/Open。
1993年1月25-29日的在圣地亚哥举行的USENIX会议首次正式介绍UTF-8。
自1996年起,微软的CAB(MS Cabinet)规格在UTF-8标准正式落实前就明确容许在任何地方使用UTF-8编码系统。但有关的编码器实际上从来没有实现这方面的规格。
结构
UTF-8使用一至六个字节为每个字符编码(尽管如此,2003年11月UTF-8被RFC 3629重新规范,只能使用原来Unicode定义的区域,U+0000到U+10FFFF,也就是说最多四个字节):
- 128个US-ASCII字符只需一个字节编码(Unicode范围由U+0000至U+007F)。
- 带有附加符号的拉丁文、希腊文、西里尔字母、亚美尼亚语、希伯来文、阿拉伯文、叙利亚文及它拿字母则需要两个字节编码(Unicode范围由U+0080至U+07FF)。
- 其他基本多文种平面(BMP)中的字符(这包含了大部分常用字,如大部分的汉字)使用三个字节编码(Unicode范围由U+0800至U+FFFF)。
- 其他极少使用的Unicode 辅助平面的字符使用四至六字节编码(Unicode范围由U+10000至U+1FFFFF使用四字节,Unicode范围由U+200000至U+3FFFFFF使用五字节,Unicode范围由U+4000000至U+7FFFFFFF使用六字节)。
对上述提及的第四种字符而言,UTF-8使用四至六个字节来编码似乎太耗费资源了。但UTF-8对所有常用的字符都可以用三个字节表示,而且它的另一种选择,UTF-16编码,对前述的第四种字符同样需要四个字节来编码,所以要决定UTF-8或UTF-16哪种编码比较有效率,还要视所使用的字符的分布范围而定。不过,如果使用一些传统的压缩系统,比如DEFLATE,则这些不同编码系统间的的差异就变得微不足道了。若顾及传统压缩算法在压缩较短文字上的效果不大,可以考虑使用Unicode标准压缩格式(SCSU)。
描述
目前有好几份关于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字符混淆,并保持标准的字节主导字符串运作顺利。
代码范围 十六进制 |
标量值(scalar value) 二进制 |
UTF-8 二进制/十六进制 |
注释 |
---|---|---|---|
000000 - 00007F 128个代码 |
00000000 00000000 0zzzzzzz | 0zzzzzzz(00-7F) | ASCII字符范围,字节由零开始 |
七个z | 七个z | ||
000080 - 0007FF 1920个代码 |
00000000 00000yyy yyzzzzzz | 110yyyyy(C0-DF) 10zzzzzz(80-BF) | 第一个字节由110开始,接着的字节由10开始 |
三个y;二个y;六个z | 五个y;六个z | ||
000800 - 00D7FF 00E000 - 00FFFF 61440个代码 [Note 1] |
00000000 xxxxyyyy yyzzzzzz | 1110xxxx(E0-EF) 10yyyyyy 10zzzzzz | 第一个字节由1110开始,接着的字节由10开始 |
四个x;四个y;二个y;六个z | 四个x;六个y;六个z | ||
010000 - 10FFFF 1048576个代码 |
000wwwxx xxxxyyyy yyzzzzzz | 11110www(F0-F7) 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序列中:
UTF-8编码字节含义
- 对于UTF-8编码中的任意字节B,如果B的第一位为0,则B独立的表示一个字符(ASCII码);
- 如果B的第一位为1,第二位为0,则B为一个多字节字符中的一个字节(非ASCII字符);
- 如果B的前两位为1,第三位为0,则B为两个字节表示的字符中的第一个字节;
- 如果B的前三位为1,第四位为0,则B为三个字节表示的字符中的第一个字节;
- 如果B的前四位为1,第五位为0,则B为四个字节表示的字符中的第一个字节;
因此,对UTF-8编码中的任意字节,根据第一位,可判断是否为ASCII字符;根据前二位,可判断该字节是否为一个字符编码的第一个字节;根据前四位(如果前两位均为1),可确定该字节为字符编码的第一个字节,并且可判断对应的字符由几个字节表示;根据前五位(如果前四位为1),可判断编码是否有错误或数据传输过程中是否有错误。
设计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分1,对更长的序列的几率就更低了。
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 (Table 3.7) [12],还是ISO 10646 (10.2 UTF-8) [13],目前都只规定了最高码位是0x10FFFF的字符的编码。下表中表示大于0x10FFFF的UTF-8编码是不符合标准的。)
码点的位数 | 码点起值 | 码点终值 | 字节序列 | Byte 1 | Byte 2 | Byte 3 | Byte 4 | Byte 5 | Byte 6 |
---|---|---|---|---|---|---|---|---|---|
7 | U+0000 | U+007F | 1 | 0xxxxxxx
| |||||
11 | U+0080 | U+07FF | 2 | 110xxxxx |
10xxxxxx
| ||||
16 | U+0800 | U+FFFF | 3 | 1110xxxx |
10xxxxxx |
10xxxxxx
| |||
21 | U+10000 | U+1FFFFF | 4 | 11110xxx |
10xxxxxx |
10xxxxxx |
10xxxxxx
| ||
26 | U+200000 | U+3FFFFFF | 5 | 111110xx |
10xxxxxx |
10xxxxxx |
10xxxxxx |
10xxxxxx
| |
31 | U+4000000 | U+7FFFFFFF | 6 | 1111110x |
10xxxxxx |
10xxxxxx |
10xxxxxx |
10xxxxxx |
10xxxxxx
|
- 在ASCII码的范围,用一个字节表示,超出ASCII码的范围就用字节表示,这就形成了我们上面看到的UTF-8的表示方法,这样的好处是当UNICODE文件中只有ASCII码时,存储的文件都为一个字节,所以就是普通的ASCII文件无异,读取的时候也是如此,所以能与以前的ASCII文件兼容。
- 大于ASCII码的,就会由上面的第一字节的前几位表示该unicode字符的长度,比如110xxxxx前三位的二进制表示告诉我们这是个2BYTE的UNICODE字符;1110xxxx是个三位的UNICODE字符,依此类推;xxx的位置由字符编码数的二进制表示的位填入。越靠右的x具有越少的特殊意义。只用最短的那个足够表达一个字符编码数的多字节串。注意在多字节串中,第一个字节的开头"1"的数目就是整个串中字节的数目。
ASCII字母继续使用1字节存储,重音文字、希腊字母或西里尔字母等使用2字节来存储,而常用的汉字就要使用3字节。辅助平面字符则使用4字节。
在UTF-8+BOM格式文件的开首,很多时都放置一个U+FEFF字符(UTF-8以EF,BB,BF代表),以显示这个文本文件是以UTF-8编码。
UTF-8的特性
UTF-8 | |
---|---|
最小码位 | 0000 |
最大码位 | 10FFFF |
每字节所占位数 | 8 bits |
Byte order | N/A |
每个字符最小字节数 | 1 |
每个字符最大字节数 | 4 |
- 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-8编码的优点
总体来说,在Unicode字符串中不可能由码点数量决定显示它所需要的长度,或者显示字符串之后在文本缓冲区中光标应该放置的位置;组合字符、变宽字体、不可打印字符和从右至左的文字都是其归因。
所以尽管在UTF-8字符串中字符数量与码点数量的关系比UTF-32更为复杂,在实际中很少会遇到有不同的情形。
更详细的说,UTF-8编码具有以下几点优点:
- ASCII是UTF-8的一个子集。因为一个纯ASCII字符串也是一个合法的UTF-8字符串,所以现存的ASCII文本不需要转换。为传统的扩展ASCII字符集设计的软件通常可以不经修改或很少修改就能与UTF-8一起使用。
- 使用标准的面向字节的排序例程对UTF-8排序将产生与基于Unicode代码点排序相同的结果。(尽管这只有有限的有用性,因为在任何特定语言或文化下都不太可能有仍可接受的文字排列顺序。)
- UTF-8和UTF-16都是可扩展标记语言文档的标准编码。所有其它编码都必须通过显式或文本声明来指定。[1](页面存档备份,存于互联网档案馆)
- 任何面向字节的字符串搜索算法都可以用于UTF-8的数据(只要输入仅由完整的UTF-8字符组成)。但是,对于包含字符记数的正则表达式或其它结构必须小心。
- UTF-8字符串可以由一个简单的算法可靠地识别出来。就是,一个字符串在任何其它编码中表现为合法的UTF-8的可能性很低,并随字符串长度增长而减小。举例说,字符值C0,C1,F5至FF从来没有出现。为了更好的可靠性,可以使用正则表达式来统计非法过长和替代值(可以查看W3 FAQ: Multilingual Forms (页面存档备份,存于互联网档案馆)上的验证UTF-8字符串的正则表达式)。
- 与UCS-2的比较:ASCII转换成UCS-2,在编码前插入一个0x0。用这些编码,会含括一些控制符,比如"或 '/',这在UNIX和一些C函数中,将会产生严重错误。因此可以肯定,UCS-2不适合作为Unicode的外部编码,也因此诞生了UTF-8。
UTF-8 编码的缺点
如果一个 UTF-8 解析器写得很差(并且与当前标准的版本不兼容),那么它接收到一些伪 UTF-8 时会将其转换成看似正确实则错误的 Unicode 输出。处理八位表示的校验例程可能遗漏一些资讯。
正则表达式可以进行很多高级的英文模糊检索。例如,[a-h]表示 a 到 h 间所有字母。
同样 GBK 编码的中文也可以这样利用正则表达式,比如在只知道一个字的读音而不知道怎么写的情况下,也可用正则表达式检索,因为 GBK 编码是按读音排序的。但是 Unicode 汉字不是按读音排序的,所以不利于用正则表达式检索。虽然正则表达式检索并未考虑中文的多音字,但是由于中文的多音字数量不多,不少多音字还是同音不同调类型的多音字,所以大多数情况下正则表达式检索是还可以接受的。不过 Unicode 汉字按部首排序,因此在只知道一个字的部首而不知道如何发音的情况下,UTF-8 可用正则表达式检索而 GBK 不行。
由于UTF-8在编码中可能有着空字符(null character,U+0000),这会导致C语言函示库以及其延伸的程序解析失败,因为这些旧有的程序库使用这个字符来标记字符串的结束。然而,之所以说“可能”,是因为这个字符是控制字符,理论上不会出现在 XML 等纯文本文件中。当万不得已要使用空字符的时候,可能的解决方法是考虑使用 Java 的变种UTF-8 ——使用 0xc0 0x80 来编码空字符。
UTF-8 的 ASCII 字符只占用一个字节,比较节省空间,但是更多字符的 UTF-8 编码占用的空间就要多出1/2,特别是中文、日文和韩文(CJK)这样的方块文字,它们大多需要三个字节。
UTF-8的派生物
虽然不是标准,但许多Windows程序(包括Windows记事本)在UTF-8编码的文件的开首加入一段字节串EF BB BF
。这是字节顺序记号U+FEFF
的UTF-8编码结果。对于没有预期要处理UTF-8的文本编辑器和浏览器会显示成ISO-8859-1字符串
。
Posix系统明确不建议使用字节序掩码EF BB BF
。[14]因为很多文本文件期望以 “#!”(Shebang)开头指示要运行的程序。Linux系统选择使用Unicode规范形式Normalization Form C(NFC),即优先使用预组装字符(precomposed character)而非组合字符序列(combining character sequence)。
2002年9月发布的Red Hat Linux 8.0才开始正式把大多数区域设置的默认编码设为UTF-8。此前是各种语言的但字节编码为主。2004年9月SuSE Linux 9.1开始,缺省编码迁移为UTF-8。
字符串处理时,使用UTF-8或locale依赖的多字节编码情形,比使用C语言wchar_t的宽字符固定宽度编码,要慢1至2个数量级。[14]
在通常用法下,Java程序语言在通过InputStreamReader
和OutputStreamWriter
读取和写入串的时候支持标准UTF-8。但是,Java也支持一种非标准的变体UTF-8,供对象的序列化,Java本地界面和在class文件中的嵌入常数时使用的modified 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操作系统使用统一码正规形式中的分解式标准等价(canonically decomposed Unicode),在文件系统中使用UTF-8编码进行文件命名,这做法通常被称为UTF-8-MAC。分解式标准等价中,预组合字符是被禁止使用的,必须以组合字符取代。
这种方法使分类变得非常简单,但是会搞混那些使用预组合字符为标准、组合字符用来显示特殊字符的软件。Mac系统的这种NFD数据是统一码正规形式(Unicode normalization)的一种格式。而其他系统,包括Windows和Linux,使用统一码规范的NFC形式,也是W3C标准使用的形式。所以通常NFD数据必须转换成NFC才能被其他平台或者网络使用。
苹果开发者专区有关于此问题的讨论:Apple Q&A 1173 (页面存档备份,存于互联网档案馆)。
MySQL字符编码集中有两套UTF-8编码实现:“utf8”和“utf8mb4”,其中“utf8”是一个字最多占据3字节空间的编码实现;而“utf8mb4”则是一个字最多占据4字节空间的编码实现,也就是UTF-8的完整实现。这是由于MySQL在4.1版本开始支持UTF-8编码(当时参考UTF-8草案版本为RFC 2279)时,为2003年,并且在同年9月限制了其实现的UTF-8编码的空间占用最多为3字节,而UTF-8正式形成标准化文档(RFC 3629)是其之后。限制UTF-8编码实现的编码空间占用一般被认为是考虑到数据库文件设计的兼容性和读取最优化,但实际上并没有达到目的,而且在UTF-8编码开始出现需要存入非基本多文种平面的Unicode字符(例如emoji字符)时导致无法存入(由于3字节的实现只能存入基本多文种平面内的字符)。直到2010年在5.5版本推出“utf8mb4”来代替、“utf8”重命名为“utf8mb3”并调整“utf8”为“utf8mb3”的别名,并不建议使用旧“utf8”编码,以此修正遗留问题。[15][16][17][18]
参阅
参考文献
外部链接
Wikiwand in your browser!
Seamless Wikipedia browsing. On steroids.
Every time you click a link to Wikipedia, Wiktionary or Wikiquote in your browser's search results, it will show the modern Wikiwand interface.
Wikiwand extension is a five stars, simple, with minimum permission required to keep your browsing private, safe and transparent.