Toggle navigation
科创
{{ getSearchSuggestionLabel(suggestion) }}
{{ part.text }}
查看更多
登录
 
注册
电波
论坛
专栏
应用
登录
注册
{{ getSearchSuggestionLabel(suggestion) }}
{{ part.text }}
查看更多
论坛
>
计算机科学
>
软件综合
>
WINDOWS
文号
t73837
阅读
29084
回复
7
收藏
2
讨论UTF-8的码位规则
acmilan
2015/10/02
软件综合
IP:四川
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,是无效的
(代理对不匹配)
(完)
[ 修改于11年2个月前 - 2015/10/02 22:04:09 ]
来自:
计算机科学
/
软件综合
/
WINDOWS
7
0
全部
只看作者
acmilan
作者
11年2个月前
IP:四川
791841
1楼
一个尾字节范围:8-B
三个多字节模式:C-D E F
两个大洞:C0-C1 F5-FF
四个小洞:E0[8-9] ED[A-B] F0[8] F4[9-B]
加载全文
引用
评论
0
张静茹
11年2个月前
IP:山东
791849
2楼
C#的字符截断很少出现问题,只有在一次字符串里有个很特殊的 占两个char大小的特殊字符 如果把它拆开会出现问题
加载全文
引用
评论
0
acmilan
作者
11年2个月前
IP:四川
791855
3楼
引用 张静茹:
C#的字符截断很少出现问题,只有在一次字符串里有个很特殊的 占两个char大小的特殊字符 如果把它拆开会出现问题
如果全是emoji的话,C#的字符串截断就会出很大问题。
加载全文
引用
评论
0
acmilan
作者
11年2个月前
修改于 11年2个月前
IP:四川
791857
4楼
UTF-16的码位规则非常简单——
1.如果D800-DBFF后边没有跟DC00-DFFF,是无效的
2.单独出现的DC00-DFFF,是无效的
由于UTF-16码位空洞特别小,因此想通过码位空洞识别UTF-16是根本不可能的。识别UTF-16只能是通过BOM。
所以实际上UTF-16是最接近二进制数据的一种编码,也是空间利用率最高的一种编码。
加载全文
引用
评论
0
acmilan
作者
11年2个月前
修改于 11年2个月前
IP:四川
791861
5楼
对于无效字符,一般将其替换为\uFFFD,它是一个带问号的菱形图案,可作为Unicode默认替换字符。
\uFFFD的UTF-8编码是EF BF BD,我们常见的『锟斤拷』就是EF BF BD EF BF BD按照GBK码解码的结果。
加载全文
引用
评论
0
acmilan
作者
11年1个月前
IP:四川
797182
6楼
UTF-8码位图解(t表示尾字节,1表示有一个尾字节,2表示有2个尾字节,3表示有3个尾字节,空白表示非法字节,下边的注解表示下一个尾字节的合法取值范围)
加载全文
引用
评论
0
acmilan
作者
11年1个月前
IP:四川
797186
7楼
如果仅仅是进行字处理而不是转换,实际上这时忽略四个小洞并不会造成什么后果。直接按期望尾字节数读取即可。
00-C1:期望0尾字节。
C2-DF:期望1尾字节。
E0-EF:期望2尾字节。
F0-F4:期望3尾字节。
F5-FF:期望0尾字节。
加载全文
引用
评论
0
全部
只看作者
在列表中查看
笔记
{{note.content}}
{{n.user.username}}
{{fromNow(n.toc)}} {{n.status === noteStatus.disabled ? "已屏蔽" : ""}} {{n.status === noteStatus.unknown ? "正在审核" : ""}} {{n.status === noteStatus.deleted ? '已删除' : ''}}
编辑
删除
{{n.status === 'disabled' ? "解除屏蔽" : "屏蔽" }}
保存
取消
我也是有底线的
保存中...
保存