字符编码与UTF-8

参考:wikipedia unicode, unicode consortium, python 编码转换



ASCII

计算机以二进制的0和1来存储和处理所有信息,其中每一个0或1被称作一个位bit,用小写b表示;八个bit为一个字节byte,用大写B表示,1B=8b,一个字节有 0000 0000 - 1111 1111 共256种不同组合,ANSI (American National Standards Institute)将这些组合与英语字符、空格以及其它一些控制符号一一对应,形成了ASCII (American Standard Code for Information Interchange)字符集编码表。ASCII收录了128个字符,它们的代码就是对应的二进制数 0000 0000 - 0111 1111 ,可以发现ASCII只占用了一个字节的后面7位,最前面的1位都是0。

[TOP]


GB2312 & GBK

计算机技术传播到非英语国家后,ASCII就无法满足需求了。开始的时候,一些国家采用一个字节里空余的0、1组合与一些字母和符号对应,形成了包括1000 0000 - 1111 1111的“扩展字符集”编码表。但是,这还是远远不够,于是,每种语言都开始重新制定自己的字符集和相应的编码表,由于单字节能表示的字符太少,这些字符集不约而同使用多字节来表示字符,如GBxxx、BIGxxx等DBCS (Double Byte Charecter Set)编码采用了两个字节,他们的规则是,如果第一个字节小于等于 0111 1111 ,则仍然表示ASCII字符;而如果大于等于 1000 0000 ,则跟下一个字节一起共16位表示一个字符。类似的方案被统称为MBCS(Multi-Byte Character Set)编码,IBM编了一个名为Code Page的索引,为这些字符集编码表统一分配页码,GBK是第936页,因此GBK有时候也被记作CP936;Microsoft Windows根据计算机设定区域,调用不同的字符集编码表,微软的程序员把他们这样的方案叫作ANSI,在简体中文Windows中,ANSI就是GBK。

GB2312字符集编码表源自1981年由中国发布的国家标准,采用两个大于等于 1000 0000 的字节连在一起(即1xxx xxxx 1xxx xxxx)表示一个字符,收录了6763个汉字以及拉丁字母、希腊字母、平假名、片假名、俄语西里尔字母等682个字符,GB2312还把ASCII里原有的数字、标点、字母按照自己的规则重新编成了两个字节,形成了“全角”字符。

GB2312字符集编码规则只包含2的14次方共16384个码位,无法满足庞大的汉字字库的需求,于是,在兼容GB2312的基础上,Microsoft利用GB 2312方案第二个字节里小于 0111 1111 的编码空间,收录GB13000.1-93全部字符,制定了GBK字符集编码方案,即“汉字内码扩展规范”,GBK中的K就是汉语拼音Kuo Zhan的意思。GBK包含2的15次方共32767个码位,收录21886个汉字和图形符号,包括GB2312中的全部字符,以及繁体汉字、部首、符号。GBK被Microsoft用在了Windows 95中,成为广泛应用的汉字字符集编码方案。

GBK并不是国家标准,而是一种“技术规范指导性文件”,其依据的GB13000一直没有被业界接受,因此,后续的GB18030兼容了GB2312和GBK,但不包括GB13000。GB18030字符集将中国部分少数民族文字、日韩汉字、繁体汉字等等包括进来,采用了一二四字节可变长度编码方式,单字节(0000 0000 - 0111 1111)与ASCII编码兼容,双字节兼容GB2312,四字节形成了更大的编码空间。由于同时期发展起来的UTF-8被广泛接受,GB18030并没有达到GBK的认可程度。

BIG5字符集诞生于1983年,1992年5月发布了修订版本,BIG5流行于台湾、香港地区,采用了两个字节对繁体汉字进行编码,收录了13461个汉字和符号。

[TOP]


unicode

这些编码表五花八门,互不兼容,使用和转换起来非常麻烦。20世纪80年代末,ISO (国际标谁化组织)决定从零开始,对所有字符进行重新编码;同时期的unicode组织(universal character encoding)也在进行类似的工作。1991年,两家组织分别发布了ISO 10646-1:1993和unicode 1.1,实际上这两个编码表是完全一致的,这个编码表被称为unicode字符集或UCS (Universal Multiple-Octet Coded Character Set)。UCS沿用传统的做法,将文本严格按照unicode字符集编码表存储和传输,如UCS-2采用16位两个字节表示一个字符,UCS-4采用32位二进制四个字节表示一个字符,这样的方案没有得到推广。因为,对于一个字节就可以满足需求的英语文本来说,UCS的方案却统一采用两个字节甚至四个字节来处理,使文件的体积大了至少一倍,不仅浪费了硬盘的空间,也降低了网络传输的效率;此外,Microsoft Windows还引入了unicode big endian和unicode little endian,使unicode的编码方案更加复杂。

这时候,终于有人提出,字符集的编码表和计算机的数字编码是可以不同的,他们制定了UTF (UCS Transfer Format)方案,将unicode的字符集编码按照UTF的规则进行重新编排,如UTF-8以8位为一个单元,UTF-16以16位为一个单元等等。其中,UTF-8伴随着互联网的兴起而迅速得到了广泛应用。那么,UTF-8究竟有哪些优势呢?

[TOP]


UTF-8

UTF-8是一种长度可变化的编码方式,一个字节作为一个处理单元。对于单字节的字符,字节的第一位设为0,如英语文本,UTF-8码和ASCII码完全相同;对于n个字节的字符(n>1),第一个字节的前n位设为1,第n+1位设为0,后面字节的前两位都设为10,这n个字节的其余空位为该字符unicode码。将一个字符的unicode码转换为UTF-8码时,应首先将高位0去除,根据剩余的编码位数决定分配的字节数,设置好标记位后,将unicode码自右向左填充在UTF-8的编码位上,最后再以0补足空位。

0xxx xxxx
110x xxxx 10xx xxxx
1110 xxxx 10xx xxxx 10xx xxxx
1111 0xxx 10xx xxxx 10xx xxxx 10xx xxxx
1111 10xx 10xx xxxx 10xx xxxx 10xx xxxx 10xx xxxx
1111 110x 10xx xxxx 10xx xxxx 10xx xxxx 10xx xxxx 10xx xxxx
... ...

有人指出unicode只是一种字符集,UTF-8才是编码。事实上,编制字符集就是为了规范字符编码,各种字符集都有自己的编码对照表。在UTF-8诞生之前,几乎所有的数字编码方案都是直接采用了字符集的编码表,包括UCS-2和UCS-4;UTF-8首次明确区分了字符集编码表和字符存储、传输的数字编码,兼顾了代码的字符容量和编码的体积,使得文本的存储和传输效率大大提高。此外,从编码规则上可以看出,UTF-8的字符边界很容易检测,局部字节或位的错误都不会导致大范围编码错乱,容错率高。

IETF(Internet Engineering Task Force)的RFC 3629协议规定UTF-8只使用unicode定义的区域,U+0000到U+10FFFF,也就是最多四个字节。

UTF-16、UTF-32也采用了和UTF-8类似的编码规则,只是将一个处理单元设置为16位和32位(UTF-32是定长编码),在某些方面提高了数据处理的效率,但在网络传输方面仍然不如UTF-8普及。关于“unicode和UTF-8的区别”,请查看这里

[TOP]


数制转换

在上文的介绍中,我们直接用二进制给出了编码规则,对于字符编码而言,二进制太长了,读、写很不方便,因此,字符集编码表上习惯用十六进制给出对照表,这里简单给出shell中数制转换的方法。

转为十进制:

$ ((num=8#11)); echo $num
9
$ ((num=16#80)); echo $num
128
$ ((num=32#11)); echo $num
33
$ ((num=64#11)); echo $num
65
$ ((num=2#10000000)); echo $num
128

十进制转为其它进制:

$ echo "obase=8;9"|bc
11
$ echo "obase=64;65"|bc
 01 01

十六进制转二进制:

$ ((num=16#80)); echo "obase=2;$num"|bc
10000000

在很多情况下,都会使用\x加两位十六进制数标记一个字节,\u加四位十六进制数标记一个字符的16位unicode码。

[TOP]


iconv

shell提供了iconv命令,可以转换文件编码。

$ iconv -l

这是查看iconv支持的字符集或编码方式。

$ iconv -f GB2312 -t UTF-8 read.txt

将GB2312编码的read.txt转为UTF-8编码并标准输出。

$ iconv -f GB2312 -t UTF-8 read.txt -o new.txt

将GB2312编码的read.txt转为UTF-8编码,并保存为new.txt。

$ curl -s www.qq.com | grep \<title\> | iconv -f GB2312 -t UTF-8

将腾讯首页的标题转为UTF-8编码。

echo命令的-e选项可以用来查看\u7f16\u7801这样的unicode编码内容,如:

$ echo -e '\u7f16\u7801'
编码

[TOP]


编码检测

在python中,可以使用chardet对字符串、文件进行编码检测。

>>> import chardet
>>> f = open('test.txt')
>>> s = f.read()
>>> f.close()
>>> chardet.detect(s)

[TOP]


python中的编码与解码

>>> u = u'编码'
>>> s1 = u.encode('GB2312')
>>> s2 = u.encode('GBK')
>>> s3 = u.encode('UTF-8')
>>> u2 = s1.decode('GB2312')
>>> print repr(s1)
'\xb1\xe0\xc2\xeb'
>>> print repr(s2)
'\xb1\xe0\xc2\xeb'
>>> print repr(s3)
'\xe7\xbc\x96\xe7\xa0\x81'
>>> print repr(u)
u'\u7f16\u7801'
>>> print repr(u2)
u'\u7f16\u7801'
>>> print str(s3)
编码

注意,下面几种情况会报错:

>>> s3 = u.decode('UTF-8')  #对unicode解码是错误的
>>> u2 = s3.encode('UTF-8')  #对字符串编码是错误的
>>> print str(u2)    #对unicode解码是错误的

str()返回的是字符串,repr()返回的是编码。

如果一个文件是GBK编码,可以通过下面的方法转成UTF-8编码。

>>> f = open('gbk.txt')
>>> s = f.read()
>>> f.close
>>> u = s.decode('GBK')
>>> f = open('gbk.txt', 'w')
>>> s = u.encode('UTF-8')
>>> f.write(s)
>>> f.close

[TOP]