UTF-8是一种将Unicode随ASCII字节流输送的编码形式,它兼容ASCII,并且不需要特殊的算法便可处理。唯一不方便的地方是它是变长编码,因此截断字符串时要小心,不然在边界处可能会产生无效字符。由于设计问题,它存在很多无效码位,我们通常可以按照这些无效码位来识别UTF-8编码。
一、全能型UTF-8
现在的Unicode字符集被称为UCS-4,最初UCS-4具有31位的编码,因此UTF-8也是能承载31位编码。这里我称这种UTF-8为全能型UTF-8。全能型UTF-8的二进制定义如下:
0-7F (xxxxxxx) => 00-7F (0xxxxxxx)
80-7FF (xxx xx'xxxxxx) => C0-DF 80-BF (110xxxxx 10xxxxxx)
800-FFFF (xxxx'xxxx xx'xxxxxx) => E0-EF 80-BF 80-BF (1110xxxx 10xxxxxx 10xxxxxx)
10000-1FFFFF (xxx'xx xxxx'xxxx xx'xxxxxx) => F0-F7 80-BF 80-BF 80-BF (11110xxx 10xxxxxx 10xxxxxx 10xxxxxx)
200000-3FFFFFF (xx' xxxxxx'xx xxxx'xxxx xx'xxxxxx) => F8-FB 80-BF 80-BF 80-BF 80-BF (111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx)
4000000-7FFFFFFF (x'xxxxxx' xxxxxx'xx xxxx'xxxx xx'xxxxxx) => FC-FD 80-BF 80-BF 80-BF 80-BF 80-BF (1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx)
也就是说,在全能型UTF-8中,字节范围不同,则功能不同,可概括如下:
字节如果是00-7F,则是ASCII
字节如果是80-BF,则是尾字节
字节如果是E0-FD,则是首字节,C0-DF跟一个尾字节,E0-EF跟两个,F0-F7跟三个,F8-FB跟四个,FC-FD跟五个
字节FE-FF,是未定义的
现在可初步总结出全能型UTF-8的基本规则:
单独的字节80-BF,是无效的
首字节C0-DF跟随1个80-BF字节,才可能是有效的
首字节E0-EF跟随2个80-BF字节,才可能是有效的
首字节F0-F7跟随3个80-BF字节,才可能是有效的
首字节F8-FB跟随4个80-BF字节,才可能是有效的
首字节FC-FD跟随5个80-BF字节,才可能是有效的
字节FE-FF,是无效的
二、Unicode码位大缩水的
虽然UCS-4设计上有31位,但是最后定下来的码位只剩下21位,值只能取000000-10FFFF。这样以来,最明显的是我们不再需要五字节或六字节编码了,于是F8-FD也成为了未定义字节。
除此之外,四字节编码也有所缩水。由于码位只能取000000-10FFFF而不是1FFFFFF,也就是只能取最高100'001111'111111'111111,因此实际上四字节编码中,首字节也只用了F0-F4,而没有使用F5-F7,并且首字节是F4时,紧挨着的一个尾字节也只能使用80-8F,而不能使用90-BF。也就是说,最大的UTF-8码位是F4 8F BF BF。
由此可以总结出下面两条规则:
字节F5-FF,是无效的
双字节F490-F4BF,是无效的
三、现实的UTF-8
随着Unicode码位的大缩水,我们的UTF-8也随之大缩水,接下来我们要对全能型UTF-8定义做个大瘦身,瘦身后的UTF-8定义如下:
0-7F (xxxxxxx) => 00-7F (0xxxxxxx)
80-7FF (xxx xx'xxxxxx) => C0-DF 80-BF (110xxxxx 10xxxxxx)
800-FFFF (xxxx'xxxx xx'xxxxxx) => E0-EF 80-BF 80-BF (1110xxxx 10xxxxxx 10xxxxxx)
10000-FFFFF (0xx'xx xxxx'xxxx xx'xxxxxx) => F0-F3 80-BF 80-BF 80-BF (11110'0xx 10xxxxxx 10xxxxxx 10xxxxxx)
100000-10FFFF (100'00 xxxx'xxxx xx'xxxxxx) => F4 80-8F 80-BF 80-BF (11110'100 10'00xxxx 10xxxxxx 10xxxxxx)
在UTF-8中,字节范围不同,则功能不同,可概括如下:
字节如果是00-7F,则是ASCII
字节如果是80-BF,则是尾字节
字节如果是E0-FD,则是首字节,C0-DF跟一个尾字节,E0-EF跟两个,F0-F4跟三个
双字节F490-F4BF,是未定义的
字节F5-FF,是未定义的
UTF-8的基本规则:
单独的字节80-BF,是无效的
首字节C0-DF跟随1个80-BF字节,才可能是有效的
首字节E0-EF跟随2个80-BF字节,才可能是有效的
首字节F0-F7跟随3个80-BF字节,才可能是有效的
双字节F490-F4BF,是无效的
字节F5-FF,是无效的
四、超长编码的问题
由于UTF-8并没有像UTF-16那样从算法上硬性规定某个码位一定按照某个字节长度编码,实际上有些人就可以动歪脑筋了。举例说明,Unicode码位0(空字符)既可以按照00来编码,又可以按照C0 80来编码,还可以按照E0 80 80来编码,还可以按照F0 80 80 80来编码,这就乱套了。一个根本不存在字节00的UTF-8字符串转换为UTF-16或UTF-32字符串之后可能会出现多个0,如果用这种方法藏一些危险代码,如rm -rf /之类的,后果可想而知。所以为了安全性的原因,大多数系统在转码时,都会将超长UTF-8编码视为无效编码。
超长UTF-8编码在现实的UTF-8中一般有这样几种表现形式:
0-7F的码位用二字节编码(110'0000x 10xxxxxx,即C0-C1 80-BF)
0-7FF的码位用三字节编码(1110'0000 10'0xxxxx 10xxxxxx,即E0 80-9F 80-BF)
0-FFFF的码位用四字节编码(11110'000 10'00xxxx 10xxxxxx 10xxxxxx,即F0 80-8F 80-BF 80-BF)
和第二节中的情况类似,我们也可以总结出以下规则,用以下的规则,就可以方便地识别超长编码,避免安全性问题。
字节C0-C1,是无效的
双字节E080-E09F,是无效的
双字节F080-F08F,是无效的
在某些应用场合下,超长编码有另外的应用。比如C字符串中是不允许出现等于0的空字符的,否则字符串会被截断,而另外一些语言则支持空字符。因此在交换字符串时,常将空字符转化为C0 80用以避免字符串被截断。然而这种做法也有一定的不安全因素,假如客户程序不做任何处理就将字符串转换为UTF-16,仍会导致字符串被截断。
五、扩展平面——四字节与六字节之争
UTF-16在远古的UCS-2时代(Unicode还是16位的时候)就产生了,那时只能支持0000-FFFF的码位。后来UCS-4加入了16个扩展平面,码位扩充到000000-10FFFF。其中小于等于00FFFF的字符仍按原来的方法表示,大于00FFFF的字符则用代理对表示。代理对首字范围是D800-DBFF (110110xx xxxxxxxx),第二字范围是DC00-DFFF (110111xx xxxxxxxx),共20位的有效位,扩展平面的字符减去010000后范围为000000-0FFFFF也刚好20位。
现在大多数软件都会先将代理对转换为大于00FFFF的扩展平面码位,然后再按四字节编码UTF-8,表现为F0-F4 80-BF 80-BF 80-BF;但是某些软件由于历史原因不能正确识别代理对,或者为了简化算法的原因,它们将代理对中的两个字各转换为两个三字节编码,表现为ED A0-AF 80-BF / ED B0-BF 80-BF。和第四节的情况一样,也乱套了。现在前者被称为标准UTF-8,后者被称为兼容UTF-8或CESU-8。
一般来说,标准UTF-8中不可能出现六字节形式,因此大多数系统都会将这种编码视为无效编码,即认为EDA0-EDBF为无效双字节。对于标准UTF-8文本,附加规则如下:
双字节EDA0-EDBF,是无效的
但是,如果需要读取含有六字节形式的兼容UTF-8文本(CESU-8文本),就不应该将EDA0-EDBF视为无效双字节,而是将它视为有效字节。反之,需要将F0以上的字节都视为无效字节,因为文本中并不需要含有四字节形式。另外,UTF-16代理对的规则也适用于CESU-8。对于兼容UTF-8文本(CESU-8文本),附加规则如下:
字节F0-FF,是无效的
双字节EDA0-EDAF后面没有跟80-BF、EDB0-EDBF、80-BF,是无效的
单独的双字节EDB0-EDBF,是无效的
具体需要支持那种形式,要看具体需求。一般来说标准UTF-8兼容性好,而兼容UTF-8(CESU-8)算法比较简单。
五、UTF-8编码规则大总结
标准UTF-8文本的规则:
单独的字节80-BF,是无效的
首字节C2-DF跟随1个80-BF字节,才可能是有效的
首字节E0-EF跟随2个80-BF字节,才可能是有效的
首字节F0-F4跟随3个80-BF字节,才可能是有效的
字节C0-C1,是无效的(超长双字节)
字节F5-FF,是无效的(F5-F7超码位、F8-FD被废弃、FE-FF未定义)
双字节E080-E09F,是无效的(超长三字节)
双字节EDA0-EDBF,是无效的(UTF-16代理对)
双字节F080-F08F,是无效的(超长四字节)
双字节F490-F4BF,是无效的(超码位)
兼容UTF-8文本(CESU-8文本)的规则:
单独的字节80-BF,是无效的
首字节C2-DF跟随1个80-BF字节,才可能是有效的
首字节E0-EF跟随2个80-BF字节,才可能是有效的
字节C0-C1,是无效的(超长双字节)
字节F0-FF,是无效的(F0-F7未使用,F8-FD被废弃,FE-FF未定义)
双字节E080-E09F,是无效的(超长三字节)
双字节EDA0-EDAF后面没有跟80-BF、EDB0-EDBF、80-BF,是无效的(代理对不匹配)
单独的双字节EDB0-EDBF,是无效的(代理对不匹配)
(完)
200字以内,仅用于支线交流,主线讨论请采用回复功能。