【字符编码系列之四】Unicode实现之UTF-8详解

在开始本篇文章之前,我默认大家已经区分好了Unicode编码(也就是代码点)和Unicode编码具体实现之间的区别。要不然,下面讲的你会不知所云的。

历史

我们知道,ISO 10646委员会定义了一个叫做Universal Character Set (UCS)的超级字符集,以囊括世界上所有的书写系统。正因为UCS现在是用4个字节编码,而实现它的则是UTF-16和UTF32之类的方案,正因这些实现方案(注意,非编码方案)是多字节的,所以导致它不兼容US-ASCII相关的系统。UTF-8也因此而诞生。UTF-8的使命之一就是对于ASCII表示的字符,本方案的编码要和ASCII完全一样。


历史上曾经的UTF-8用1~6个字节来编码字符,也就是说,与此对应的抽象代码点可以达到U+7FFFFFFF。但是人们发现,对于代码点来说,根本用不到4个字节编码,只用21位就可以完全包含世界上所有的书写系统了,即合法代码点为0x0000~0x10FFFF。所以随着Unicode规定合法的代码点范围是0x0000 0000 ~0x0010 FFFF,RFC 3629宣布,以前的UTF-8(RFC 2279)标准作废,新标准中UTF-8用1~4个字节来编码字符。


对于Unicode相关的编码,总共有五种:UTF-8, UCS-2, UTF-16, UCS-4 and UTF-32.要是有人非要加上UTF-7那也可以吧。
本系列后续文章会依次讲到上述所有实际编码方案。


UTF-8

UTF-8,顾名思义,是以字节为单位的。并不是说用UTF-8编码的字符就是一个字节,而是说,它是以字节为单位来增加的,1字节,2字节,3字节,4字节。
而UTF-16,则是以16位为单位来增加的,2字节,4字节。
UTF-8和UTF-16都是变长编码的,我们可以通过下面这张表来清楚地看出UTF-8的具体实现方式:

     字符  代码点空间    |        UTF-8 字节序列
      (十六进制表示)     |         (二进制表示)
   --------------------+---------------------------------------------
   0000 0000-0000 007F | 0xxxxxxx
   0000 0080-0000 07FF | 110xxxxx 10xxxxxx
   0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
   0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

右边的x表示的是二进制中的一位。

首先,从上图可以很明显地看出,对于US-ASCII字符来说,UTF-8和ASCII编码是完全一样的。这一点深受老美的喜欢。

同时,从UTF-8的编码实现中可以看出,它是属于自同步编码。即当我们读到一个字节,如果它是0开头的,那么本字节就是这个字符的编码;如果它是以110开头的,那么本字节加上下一个字节,就是这个字符的编码,以此类推。也就是说,第一个字节表明了本字符编码的长度是几个字节。正因为UTF-8是自同步编码,所以我们可以从字节流的任一位置开始读入,最多跳过3个字节,我们就可以找到字符的边界。

在UTF-8编码中,C0, C1, F5~FF是不允许出现的。这是规定。同时,对于代码点为U+D800~U+DFFF之间的代码,UTF-8是拒绝为其进行UTF-8编码的。这是因为对于UTF-16来说,这一区间是作为代理对儿来使用的,并非直接表示字符。为了和UTF-16保持字符一一对应,做出了此规定。(题外话,我们可以看出,U+D800~U+DFFF总共有2048个,也就是2K个代码点。)一般的UTF-8与UTF-16之间的转换,也是通过中间方 Unicode代码点来实现的。

对于解码,同样需要注意,比如对于貌似符合UTF-8的C080编码来说,其实是非法编码,在解码时要予以跳过。

0xC080
1100 0000 1000 0000

如果真的按照上面的表格进行解码的话,我们得到的代码点是U+0000,这显然是错误的。

再多说一句,对于想要实现UTF-8转换的程序员们,要注意到可能由非法UTF-8序列引起的安全问题。


BOM
讲UTF-8,那就必须要讲一下BOM(Byte order mark),也叫做字节次序标记。
在Unicode编码的代码点中,U+FEFF称之为零宽非间断空格(ZERO WIDTH NO-BREAK SPACE)。对于其他的编码来说,这就是著名的BOM,在新标准中,我们只把U+FEFF用于字符流的开关以表示大小端。
但对于以字节为递增单位的UTF-8来说,表示大小端是没有意义的,因为大小端指的是单位内字节的存储方式。所以对UTF-8来说,它将U+FEFF替换成了EFBBBF。这就是UTF-8的BOM,也就是说,只要开头出现EFBBBF,就表明这段字符流的编码方式是UTF-8的。


汉字

CJK(中、日、韩)或CJKV(中、日、韩、越)的三四种文字占用了Unicode中0x3000到0x9FFF的部分,通过上面的转换表格我们可以清楚地看到,这一部分对应的UTF-8编码是三个字节的。是的,只要是用UTF-8编码的汉字,就一定是三个字节。这就是一个坑啊。(多说一句,希望已经看到这里的同学,已经弄清Unicode编码和UTF系列实现之间的区别了)加利福尼亚的程序呵呵路过。


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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值