python编码问题

qadnkz

贡献于2014-08-31

字数:17405 关键词: Python开发

一直在受中文的困扰,花了好长时间来整理学习这个东西,到底是什么原理,一些东东给大家看看(一) 遇到过的编码类型,貌似不全 Gbk Unicode ASCII UTF-8 UTF-16 GB2312 WINDOWS-936 cp936 encode()编码 decode()解码 一、几种常见的编码格式。 1.1、ascii,用1个字节表示。 1.2、UTF-8,用1个至三个字节表示,表示ascii码时只占用1个字节,ascii编码是UTF-8的子集。 1.3、UTF-16,用2个字节表示,在python中,unicode的含义就是UTF-16。 二、python源文件的编码与解码,我们写的python程序从产生到执行的过程如下: 编辑器---->源代码---->解释器---->输出结果 2.1、编辑器决定源代码的编码格式(在编辑器中设定) 2.2、也必须要解释器知道源代码的编码格式(很遗憾很难从编码的数据获知源文件的编码格式) 2.3、补充:在Windows下当用UltraEdit把源代码存成UTF-8时,会在文件中记录BOM标志(不必祥究)这样ActivePython解释器会自动识别源文件是UTF-8格式,但是如果用eclipse编辑源文件,虽然在编辑器中指定文件编码为UTF-8,但是因为没有记入BOM标志,所以必须在源文件开始处加上#coding=utf-8,用注释来提示解释器源文件的编码方式挺有意思。 2.4、举例:例如我们要向终端输出"我是中国人"。  1. #coding=utf-8     告诉python解释器用的是utf-8编码,我用的是eclipse+pydev      2. print "我是中国人"  #源文件本身也要存成UTF-8编码     三、编码的转换,两种编码的转换要用UTF-16作为中转站。 举例:如果有一个文本文件jap.txt,里面有内容 "私は中国人です。",编码格式是日文编码SHIFT_JIS, 还有一个文本文件chn.txt,内容是"中华人民共和国",编码格式是中文编码GB2312。 我们如何把两个文件里的内容合并到一起并存储到utf.txt中并且不显示乱码呢,可以采用把两个文件的内容都转成UTF-8格式,因为UTF-8里包含了中文编码和日文编码。 1. #coding=utf-8      2.      3. try:      4.     JAP=open("e:/jap.txt","r")      5.     CHN=open("e:/chn.txt","r")      6.     UTF=open("e:/utf.txt","w")      7.           8.     jap_text=JAP.readline()      9.     chn_text=CHN.readline()      10.     #先decode成UTF-16,再encode成UTF-8      11.     jap_text_utf8=jap_text.decode("SHIFT_JIS").encode("UTF-8") #不转成utf-8也可以      12.     chn_text_utf8=chn_text.decode("GB2312").encode("UTF-8")#编码方式大小写都行utf-8也一样      13.     UTF.write(jap_text_utf8)      14.     UTF.write(chn_text_utf8)      15.           16. except IOError,e:      17.     print "open file error",e     四、Tk库支持ascii,UTF-16,UTF-8 1. #coding=utf-8      2.      3. from Tkinter import *      4.      5. try:      6.     JAP=open("e:/jap.txt","r")      7.     str1=JAP.readline()      8.      9. except IOError,e:      10.     print "open file error",e      11.      12. root=Tk()      13.      14. label1=Label(root,text=str1.decode("SHIFT_JIS")) #如果没有decode则显示乱码      15. label1.grid()      16.      17. root.mainloop()     以上是学习python处理python编码的基本过程,希望对大家有帮助。 二、Python、Unicode和中文 Windows软件测试PythonUnixSSH http://blog.chinaunix.net/u/3204/showart_389639.html http://www.woodpecker.org.cn/diveintopython/xml_processing/unicode.html python的中文问题一直是困扰新手的头疼问题,这篇文章将给你详细地讲解一下这方面的知识。当然,几乎可以确定的是,在将来的版本中,python会彻底解决此问题,不用我们这么麻烦了。 先来看看python的版本: >>> import sys >>> sys.version '2.5.1 (r251:54863, Apr 18 2007, 08:51:08) [MSC v.1310 32 bit (Intel)]' (一) 用记事本创建一个文件ChineseTest.py,默认ANSI: s = "中文" print s 测试一下瞧瞧: E:\Project\Python\Test>python ChineseTest.py   File "ChineseTest.py", line 1 SyntaxError: Non-ASCII character '\xd6' in file ChineseTest.py on line 1, but no encoding declared; see http://www.pytho n.org/peps/pep-0263.html for details 偷偷地把文件编码改成UTF-8: E:\Project\Python\Test>python ChineseTest.py   File "ChineseTest.py", line 1 SyntaxError: Non-ASCII character '\xe4' in file ChineseTest.py on line 1, but no encoding declared; see http://www.pytho n.org/peps/pep-0263.html for details 无济于事。。。 既然它提供了网址,那就看看吧。简单地浏览一下,终于知道如果文件里有非ASCII字符,需要在第一行或第二行指定编码声明。把ChineseTest.py文件的编码重新改为ANSI,并加上编码声明: # coding=gbk s = "中文" print s 再试一下: E:\Project\Python\Test>python ChineseTest.py 中文 正常咯:) (二) 看一看它的长度: # coding=gbk s = "中文" print len(s) 结果:4。 s这里是str类型,所以计算的时候一个中文相当于两个英文字符,因此长度为4。 我们这样写: # coding=gbk s = "中文" s1 = u"中文" s2 = unicode(s, "gbk") #省略参数将用python默认的ASCII来解码 s3 = s.decode("gbk") #把str转换成unicode是decode,unicode函数作用与之相同 print len(s1) print len(s2) print len(s3) 结果: 2 2 2 (三) 接着来看看文件的处理: 建立一个文件test.txt,文件格式用ANSI,内容为: abc中文 用python来读取 # coding=gbk print open("Test.txt").read() 结果:abc中文 把文件格式改成UTF-8: 结果:abc涓枃 显然,这里需要解码: # coding=gbk import codecs print open("Test.txt").read().decode("utf-8") 结果:abc中文 上面的test.txt我是用Editplus来编辑的,但当我用Windows自带的记事本编辑并存成UTF-8格式时, 运行时报错: Traceback (most recent call last):   File "ChineseTest.py", line 3, in     print open("Test.txt").read().decode("utf-8") UnicodeEncodeError: 'gbk' codec can't encode character u'\ufeff' in position 0: illegal multibyte sequence 原来,某些软件,如notepad,在保存一个以UTF-8编码的文件时,会在文件开始的地方插入三个不可见的字符(0xEF 0xBB 0xBF,即BOM)。 因此我们在读取时需要自己去掉这些字符,python中的codecs module定义了这个常量: # coding=gbk import codecs data = open("Test.txt").read() if data[:3] == codecs.BOM_UTF8: data = data[3:] print data.decode("utf-8") 结果:abc中文 (四)一点遗留问题 在第二部分中,我们用unicode函数和decode方法把str转换成unicode。为什么这两个函数的参数用"gbk"呢? 第一反应是我们的编码声明里用了gbk(# coding=gbk),但真是这样? 修改一下源文件: # coding=utf-8 s = "中文" print unicode(s, "utf-8") 运行,报错: Traceback (most recent call last):   File "ChineseTest.py", line 3, in     s = unicode(s, "utf-8") UnicodeDecodeError: 'utf8' codec can't decode bytes in position 0-1: invalid data 显然,如果前面正常是因为两边都使用了gbk,那么这里我保持了两边utf-8一致,也应该正常,不至于报错。 更进一步的例子,如果我们这里转换仍然用gbk: # coding=utf-8 s = "中文" print unicode(s, "gbk") 结果:中文 翻阅了一篇英文资料,它大致讲解了python中的print原理: When Python executes a print statement, it simply passes the output to the operating system (using fwrite() or something like it), and some other program is responsible for actually displaying that output on the screen. For example, on Windows, it might be the Windows console subsystem that displays the result. Or if you're using Windows and running Python on a Unix box somewhere else, your Windows SSH client is actually responsible for displaying the data. If you are running Python in an xterm on Unix, then xterm and your X server handle the display. To print data reliably, you must know the encoding that this display program expects. 简单地说,python中的print直接把字符串传递给操作系统,所以你需要把str解码成与操作系统一致的格式。Windows使用CP936(几乎与gbk相同),所以这里可以使用gbk。 最后测试: # coding=utf-8 s = "中文" print unicode(s, "cp936") 结果:中文 Python的中文处理及其它 Posted by 令狐虫 前段时间猛禽在写他的第一个Python程序,期间一直向我抱怨说Python的中文 处理有这样那样的问题。说句实话我之前并没有花过很大的精力在这种编码处理方面,不过我很纳闷,难道Python的编码处理能力真这么弱么?于是去做了一些试验,现在来简单的聊几句。 我们知道在Unicode出现之前,处理中文是一件非常麻烦的事情。因为没有自己的独立编码,GB(GB2312、GBK、GB18030)码是 用2个多个ASCII字符来表示的,这就带来了和ASCII码的区分、字数统计等等一系列的问题。而在Unicode中,中文、英文以及其它很多字符都有 自己独立的编码,这样一来,就没有上述问题了。因此,在可能的环境中,使用Unicode处理中文,是最好的选择。 一般来说,对中文处理的主要流程可以如下: 原始字符串用相应的编码转为Unicode 中间处理一律采用Unicode 将Unicode转成需要的编码输出 在Python中,跟Unicode相关的元素有以下几个:u前缀、unicode函数、codecs。他们有什么联系和区别么? u前缀表示后面跟的那个字符串常量是一个Unicode字符串。但它仅仅表示这是一个Unicode字符串,对字符串本身的编码却没有做任何改变。比如: s = u"测试" s的值是: u'\xb2\xe2\xca\xd4' 而 s = "测试" s的值是: '\xb2\xe2\xca\xd4' 可以看到,除了前面的u前缀外,字符内部编码没有丝毫差别。因此,我们要得到一个Unicode的中文字符串,u前缀并不是一个好方法。那么u前缀有什么作用呢?我的猜想是对非英语字符可能能起作用,未经证实,猜错不管。 和u前缀不同,unicode函数有一个转换编码的过程,在转换过程中,用到了codecs。 s = unicode("测试", "gb2312") s的值是: u'\u6d4b\u8bd5' 可以看到,这时的s,是一个真正的双字节Unicode编码字符串了。 结合上面的中文处理流程,我们可以得到一个比较一般的Python中文处理的流程: 将欲处理的字符串用unicode函数,以正确的编码转换为Unicode 在程序中统一用Unicode字符串进行操作 输出时,使用encode方法,将Unicode再转换为所需的编码。 下面是一个简单的演示,用re库查询一个中文字符串并打印: >>> p = re.compile(unicode("测试(.*)", "gb2312")) >>> s = unicode("测试一二三", "gb2312") >>> for i in p.findall(s):       print i.encode("gb2312") 一二三 有几点要说明一下: 所谓“正确的”编码,指得是指定编码和字符串本身的编码必须一致。这个其实并不那么容易判断,一般来说,我们直接输入的简体中文字符,有两种可能的编码:GB2312(GBK、GB18030)、以及UTF-8。 encode成本地编码的时候,必须要保证目标编码中存在欲转换字符的内码。encode这种操作一般是通过一个本地编码对应Unicode的编 码转换表来进行的,事实上每个本地编码只能映射到Unicode的一部分。但是映射的区域是不同的,比如Big-5对应的Unicode的编码范围和 GBK对应的就不一样(实际上这两个编码有部分范围是重叠的)。所以,Unicode的一些字符(比如本身就是从GB2312转换来的那些),可以映射到 GBK,但未必可以映射到Big-5,如果你想转换到Big-5,很有可能就会出现编码找不到的异常。但UTF-8的码表范围实际上和Unicode是一 样的(只是编码形式不同而已),所以,理论上来说,任何本地编码的字符,都可以被转换到UTF-8。 PS1: GB2312、GBK、GB18030本质上是同一种编码标准。只是在前者的基础上扩充了字符数量。 PS2: UTF-8和GB编码不兼容 三、实战:Python的中文处理   1.   http://blog.csdn.net/woaixiaoqx/article/details/6642214 2. 一、使用中文字符   3.     在python源码中如果使用了中文字符,运行时会有错误,解决的办法是在源码的开头部分加入字符编码的声明,下面是一个例子:   4.       #!/usr/bin/env python    5.       # -*- coding: cp936 -*-    6.     Python Tutorial中指出,python的源文件可以编码ASCII以外的字符集,最好的做法是在#!行后面用一个特殊的注释行来定义字符集:    7.         # -*- coding: encoding -*-    8. 根据这个声明,Python会尝试将文件中的字符编码转为encoding编码,并且,它尽可能的将指定地编码直接写成Unicode文本。   9.     注意,coding:encoding只是告诉Python文件使用了encoding格式的编码,但是编辑器可能会以自己的方式存储.py文件,因此最后文件保存的时候还需要编码中选指定的ecoding才行。   10.    11. 二、中文字符的存储   12.         >>> str = u"中文"   13.         >>> str   14.         u'\xd6\xd0\xce\xc4'   15.         >>> str = "中文"   16.         >>> str   17.         '\xd6\xd0\xce\xc4'   18.     u"中文"只是声明unicode,实际的编码并没有变。这样子就发生变化了:   19.         >>> str = "中文"   20.         >>> str   21.         '\xd6\xd0\xce\xc4'   22.         >>> str = str.decode("gb2312")   23.         >>> str   24.         u'\u4e2d\u6587'   25. 更进一步:   26.         >>> s = '中文'   27.         >>> s.decode('gb2312')   28.         u'\u4e2d\u6587'   29.         >>> len(s)   30.         4   31.         >>> len(s.decode('gb2312'))   32.         2   33.         >>> s = u'中文'   34.         >>> len(s)   35.         4   36.         >>> s = '中文test'   37.         >>> len(s)   38.         8   39.         >>> len(s.decode('gb2312'))   40.         6   41.         >>> s = '中文test,'   42.         >>> len(s)   43.         10   44.         >>> len(s.decode('gb2312'))   45.         7   46.     可以看出,对于实际Non-ASCII编码存储的字符串,python可以正确的识别出其中的中文字符以及中文上下文中的标点符号。   47.     前缀“u”表示“后面这个字符串“是一个Unicode字符串”,这仅仅是一个声明,并不表示这个字符串就真的是Unicode了;就好比某正太声称自己已满18岁,但实际上他的真实年龄并不确定,现在体育界年龄造假可不稀罕幺!   48.     那么声明成u有什么作用呢?对于Python来说,只要你声明某字符串是Unicode,它就会用Unicode的一套机制对它进行处理。比方说,做字符串操作的时候会动用到内部的Unicode处理函数,保存的时候以Unicode字符(双字节)进行保存。等等。显而易见,对于一个实际上并不是Unicode的字符串,做Unicode动作的处理,是有可能会出问题的。u前缀只适用于你的字符串常量真的是Unicode的情况。   49.    50. 三、中文字符的IO操作   51.     用python处理字符串很容易,但是在处理中文的时候需要注意一些问题。比如:   52.         a = "我们是python爱好者"   53.         print a[0]   54. 只能输出“我”字的前半部分,要想输出整个的“我”字还需要:   55.         b = a[0:2]   56.         print b   57. 才行,很不方便,并且当一段文本中同时有中英文如何处理?最好的办法就是转换为unicode。像这样:   58.         c = unicode(a, "gb2312")   59.         print c[0]   60. 这个时候c的下标对应的就是每一个字符,不再是字节,并且通过len(c)就可以获得字符数!还可以很方便的转换为其他编码,比如转换为utf-8:   61.        d = c.encode("utf-8")   62.    63. 四、   64. 将字符串看作是字节的序列,而则将其看作是字符的序列,单个字符可能占用多个字节;字节相对于字符,其在存储层次中更低一些。   65. str转换为unicode要decode,可以这样想,因为要把字节序列解释成字符序列,字节序列是底层的存放方式,解码(decode)成更高层的字符以便使用;同理,unicode转换为str要encode,就象信息编码(encode)后才存储一样:   66.         s.decode(encoding)     to    67.         u.encode(encoding)     to    68.     例如:   69.         >>> s = 'str'   70.         >>> type(s)   71.            72.         >>> type(s.decode())   73.            74.         >>> s = u'str'   75.         >>> type(s)   76.            77.         >>> type(s.encode())   78.            79. 处理中文数据时最好采用如下方式:   80.    1. Decode early(尽早decode, 将文件中的内容转化成unicode再进行下一步处理)   81.    2. Unicode everywhere (程序内部处理都用unicode)   82.    3. Encode late (最后encode回所需的encoding, 例如把最终结果写进结果文件)   83. 下面是一个简单的演示,用re库查询一个中文字符串并打印:   84.     >>> p = re.compile(unicode("测试(.*)", "gb2312"))   85.     >>> s = unicode("测试一二三", "gb2312")   86.     >>> for i in p.findall(s):   87.                   print i.encode("gb2312")   88.     一二三   89.    90. 五、跨平台处理技巧   91.     如果一个project必须在两个平台上开发,程序应该使用同样的encoding,比如要求所有的文件都使用UTF-8,如果实在不能统一(一般是为了满足许多所谓专家学者莫名其妙的要求),可以退而求其次,用当前系统编码决定文件内的编码:   92.         import locale   93.         import string   94.         import re   95.         #根据当前系统的encoding构造需要的编码取值    96.         lang = string.upper(locale.setlocale(locale.LC_ALL, ""))   97.         textencoding = None   98.         #检查编码的值是不是满足我们需要的情况    99.         if re.match("UTF-8", lang) != None:   100.             # UTF-8编码    101.             textencoding = "utf-8"   102.         elif re.match(r"CHINESE|CP936", lang):   103.             # Windows下的GB编码    104.             textencoding = "gb18030"   105.         elif re.match(r"GB2312|GBK|GB18030", lang):   106.             # Linux下的GB编码    107.             textencoding = "gb18030"   108.         else:   109.             # 其他情况,抛个错误吧    110.             raise UnicodeError   111.         fd = file(filename, "r")   112.         fulltextlist = fd.readlines()   113.         # 把每一行转换成unicode    114.         for each in len(fulltextlist):   115.             fulltextlist[i] = unicode(each, textencoding)   116.         fd.close()   117.         # 如果要打印的话,可以用text.encode(encoding)来恢复成多字节编码    118.    119. 小结   120.     一个比较一般的Python中文处理的流程:   121.     * 将欲处理的字符串用unicode函数以正确的编码转换为Unicode                        122.     * 在程序中统一用Unicode字符串进行操作                                             123.     * 输出时,使用encode方法,将Unicode再转换为所需的编码                           124. 有几点要说明一下:   125.     * 所谓“正确的”编码,指得是指定编码和字符串本身的编码必须一致。这个其实并不那么容易判断,一般来说,我们直接输入的简体中文字符,有两种可能的编码:GB2312(GBK、GB18030)、以及UTF-8   126.     * encode成本地编码的时候,必须要保证目标编码中存在欲转换字符的内码。encode这种操作一般是通过一个本地编码对应Unicode的编码转换表来进行的,事实上每个本地编码只能映射到Unicode的一部分。但是映射的区域是不同的,比如Big-5对应的Unicode的编码范围和 GBK对应的就不一样(实际上这两个编码有部分范围是重叠的)。所以,Unicode的一些字符(比如本身就是从GB2312转换来的那些),可以映射到 GBK,但未必可以映射到Big-5,如果你想转换到Big-5,很有可能就会出现编码找不到的异常。但UTF-8的码表范围实际上和Unicode是一样的(只是编码形式不同而已),所以,理论上来说,任何本地编码的字符,都可以被转换到UTF-8   127.     * GB2312、GBK、GB18030本质上是同一种编码标准。只是在前者的基础上扩充了字符数量   128.     * UTF-8和GB编码不兼容   四、Unicode,UTF8等编码规范   原文地址:Unicode,UTF8等编码规范作者:K_H_H http://hi.baidu.com/flintt/blog/item/83b7a0af1542d1cf7dd92a40.html 先从ASCII说起。ASCII是用来表示英文字符的一种编码规范,每个ASCII字符占用1个字节(8bits) 因此,ASCII编码可以表示的最大字符数是256,其实英文字符并没有那么多,一般只用前128个(最高位为0),其中包括了控制字符、数字、大小写字母和其他一些符号。 而最高位为1的另128个字符被成为“扩展ASCII”,一般用来存放英文的制表符、部分音标字符等等的一些其他符号 这种字符编码规范显然用来处理英文没有什么问题。(实际上也可以用来处理法文、德文等一些其他的西欧字符,但是不能和英文通用),但是面对中文、阿拉伯文之类复杂的文字,255个字符显然不够用 于是,各个国家纷纷制定了自己的文字编码规范,其中中文的文字编码规范叫做“GB2312-80”,它是和ASCII兼容的一种编码规范,其实就是利用扩展ASCII没有真正标准化这一点,把一个中文字符用两个扩展ASCII字符来表示。 但是这个方法有问题,最大的问题就是,中文文字没有真正属于自己的编码,因为扩展ASCII码虽然没有真正的标准化,但是PC里的ASCII码还是有一个事实标准的(存放着英文制表符),所以很多软件利用这些符号来画表格。这样的软件用到中文系统中,这些表格符就会被误认作中文字,破坏版面。而且,统计中英文混合字符串中的字数,也是比较复杂的,我们必须判断一个ASCII码是否扩展,以及它的下一个ASCII是否扩展,然后才“猜”那可能是一个中文字。 总之当时处理中文是很痛苦的。而更痛苦的是GB2312是国家标准,台湾当时有一个Big5编码标准,很多编码和GB是相同的,所以……,嘿嘿。 这时候,我们就知道,要真正解决中文问题,不能从扩展ASCII的角度入手,也不能仅靠中国一家来解决。而必须有一个全新的编码系统,这个系统要可以将中文、英文、法文、德文……等等所有的文字统一起来考虑,为每个文字都分配一个单独的编码,这样才不会有上面那种现象出现。 于是,Unicode诞生了。 Unicode有两套标准,一套叫UCS-2(Unicode-16),用2个字节为字符编码,另一套叫UCS-4(Unicode-32),用4个字节为字符编码。 以目前常用的UCS-2为例,它可以表示的字符数为2^16=65535,基本上可以容纳所有的欧美字符和绝大部分的亚洲字符 。 UTF-8的问题后面会提到 。 在Unicode里,所有的字符被一视同仁。汉字不再使用“两个扩展ASCII”,而是使用“1个Unicode”,注意,现在的汉字是“一个字符”了,于是,拆字、统计字数这些问题也就自然而然的解决了。 但是,这个世界不是理想的,不可能在一夜之间所有的系统都使用Unicode来处理字符,所以Unicode在诞生之日,就必须考虑一个严峻的问题:和ASCII字符集之间的不兼容问题。 我们知道,ASCII字符是单个字节的,比如“A”的ASCII是65。而Unicode是双字节的,比如“A”的Unicode是0065,这就造成了一个非常大的问题:以前处理ASCII的那套机制不能被用来处理Unicode了。 另一个更加严重的问题是,C语言使用''作为字符串结尾,而Unicode里恰恰有很多字符都有一个字节为0,这样一来,C语言的字符串函数将无法正常处理Unicode,除非把世界上所有用C写的程序以及他们所用的函数库全部换掉。 于是,比Unicode更伟大的东东诞生了,之所以说它更伟大是因为它让Unicode不再存在于纸上,而是真实的存在于我们大家的电脑中。那就是:UTF 。 UTF= UCS Transformation Format UCS转换格式 它是将Unicode编码规则和计算机的实际编码对应起来的一个规则。现在流行的UTF有2种:UTF-8和UTF-16 。 其中UTF-16和上面提到的Unicode本身的编码规范是一致的,这里不多说了。而UTF-8不同,它定义了一种“区间规则”,这种规则可以和ASCII编码保持最大程度的兼容。 UTF-8有点类似于Haffman编码,它将Unicode编码为00000000-0000007F的字符,用单个字节来表示; 00000080-000007FF的字符用两个字节表示 00000800-0000FFFF的字符用3字节表示 因为目前为止Unicode-16规范没有指定FFFF以上的字符,所以UTF-8最多是使用3个字节来表示一个字符。但理论上来说,UTF-8最多需要用6字节表示一个字符。 在UTF-8里,英文字符仍然跟ASCII编码一样,因此原先的函数库可以继续使用。而中文的编码范围是在0080-07FF之间,因此是2个字节表示(但这两个字节和GB编码的两个字节是不同的),用专门的Unicode处理类可以对UTF编码进行处理。 下面说说中文的问题。 由于历史的原因,在Unicode之前,一共存在过3套中文编码标准。 GB2312-80,是中国大陆使用的国家标准,其中一共编码了6763个常用简体汉字。Big5,是台湾使用的编码标准,编码了台湾使用的繁体汉字,大概有8千多个。HKSCS,是中国香港使用的编码标准,字体也是繁体,但跟Big5有所不同。 这3套编码标准都采用了两个扩展ASCII的方法,因此,几套编码互不兼容,而且编码区间也各有不同 因为其不兼容性,在同一个系统中同时显示GB和Big5基本上是不可能的。当时的南极星、RichWin等等软件,在自动识别中文编码、自动显示正确编码方面都做了很多努力。 他们用了怎样的技术我就不得而知了,我知道好像南极星曾经以同屏显示繁简中文为卖点。 后来,由于各方面的原因,国际上又制定了针对中文的统一字符集GBK和GB18030,其中GBK已经在Windows、Linux等多种操作系统中被实现。 GBK兼容GB2312,并增加了大量不常用汉字,还加入了几乎所有的Big5中的繁体汉字。但是GBK中的繁体汉字和Big5中的几乎不兼容。 GB18030相当于是GBK的超集,比GBK包含的字符更多。据我所知目前还没有操作系统直接支持GB18030。 谈谈Unicode编码,简要解释UCS、UTF、BMP、BOM等名词 这是一篇程序员写给程序员的趣味读物。所谓趣味是指可以比较轻松地了解一些原来不清楚的概念,增进知识,类似于打RPG游戏的升级。整理这篇文章的动机是两个问题: 问题一: 使用Windows记事本的“另存为”,可以在GBK、Unicode、Unicode big endian和UTF-8这几种编码方式间相互转换。同样是txt文件,Windows是怎样识别编码方式的呢? 我很早前就发现Unicode、Unicode big endian和UTF-8编码的txt文件的开头会多出几个字节,分别是FF、FE(Unicode),FE、FF(Unicode big endian),EF、BB、BF(UTF-8)。但这些标记是基于什么标准呢? 问题二: 最近在网上看到一个ConvertUTF.c,实现了UTF-32、UTF-16和UTF-8这三种编码方式的相互转换。对于 Unicode(UCS2)、GBK、UTF-8这些编码方式,我原来就了解。但这个程序让我有些糊涂,想不起来UTF-16和UCS2有什么关系。 查了查相关资料,总算将这些问题弄清楚了,顺带也了解了一些Unicode的细节。写成一篇文章,送给有过类似疑问的朋友。本文在写作时尽量做到通俗易懂,但要求读者知道什么是字节,什么是十六进制。 0、big endian和little endian big endian和little endian是CPU处理多字节数的不同方式。例如“汉”字的Unicode编码是6C49。那么写到文件里时,究竟是将6C写在前面,还是将49写在前面?如果将6C写在前面,就是big endian。还是将49写在前面,就是little endian。 “endian”这个词出自《格列佛游记》。小人国的内战就源于吃鸡蛋时是究竟从大头(Big-Endian)敲开还是从小头(Little-Endian)敲开,由此曾发生过六次叛乱,其中一个皇帝送了命,另一个丢了王位。 我们一般将endian翻译成“字节序”,将big endian和little endian称作“大尾”和“小尾”。 1、字符编码、内码,顺带介绍汉字编码 字符必须编码后才能被计算机处理。计算机使用的缺省编码方式就是计算机的内码。早期的计算机使用7位的ASCII编码,为了处理汉字,程序员设计了用于简体中文的GB2312和用于繁体中文的big5。 GB2312(1980年)一共收录了7445个字符,包括6763个汉字和682个其它符号。汉字区的内码范围高字节从B0-F7,低字节从A1-FE,占用的码位是72*94=6768。其中有5个空位是D7FA-D7FE。 GB2312支持的汉字太少。1995年的汉字扩展规范GBK1.0收录了21886个符号,它分为汉字区和图形符号区。汉字区包括21003个字符。2000年的GB18030是取代GBK1.0的正式国家标准。该标准收录了27484个汉字,同时还收录了藏文、蒙文、维吾尔文等主要的少数民族文字。现在的PC平台必须支持GB18030,对嵌入式产品暂不作要求。所以手机、MP3一般只支持GB2312。 从ASCII、GB2312、GBK到GB18030,这些编码方法是向下兼容的,即同一个字符在这些方案中总是有相同的编码,后面的标准支持更多的字符。在这些编码中,英文和中文可以统一地处理。区分中文编码的方法是高字节的最高位不为0。按照程序员的称呼,GB2312、GBK到GB18030 都属于双字节字符集 (DBCS)。 有的中文Windows的缺省内码还是GBK,可以通过GB18030升级包升级到GB18030。不过GB18030相对GBK增加的字符,普通人是很难用到的,通常我们还是用GBK指代中文Windows内码。 这里还有一些细节: GB2312的原文还是区位码,从区位码到内码,需要在高字节和低字节上分别加上A0。 在DBCS中,GB内码的存储格式始终是big endian,即高位在前。 GB2312的两个字节的最高位都是1。但符合这个条件的码位只有128*128=16384个。所以GBK和GB18030的低字节最高位都可能不是1。不过这不影响DBCS字符流的解析:在读取DBCS字符流时,只要遇到高位为1的字节,就可以将下两个字节作为一个双字节编码,而不用管低字节的高位是什么。 2、Unicode、UCS和UTF 前面提到从ASCII、GB2312、GBK到GB18030的编码方法是向下兼容的。而Unicode只与ASCII兼容(更准确地说,是与ISO-8859-1兼容),与GB码不兼容。例如“汉”字的Unicode编码是6C49,而GB码是BABA。 Unicode也是一种字符编码方法,不过它是由国际组织设计,可以容纳全世界所有语言文字的编码方案。Unicode的学名是"Universal Multiple-Octet Coded Character Set",简称为UCS。UCS可以看作是"Unicode Character Set"的缩写。 根据维基百科全书(http://zh.wikipedia.org/wiki/)的记载:历史上存在两个试图独立设计Unicode的组织,即国际标准化组织(ISO)和一个软件制造商的协会(unicode.org)。ISO开发了ISO 10646项目,Unicode协会开发了Unicode项目。 在1991年前后,双方都认识到世界不需要两个不兼容的字符集。于是它们开始合并双方的工作成果,并为创立一个单一编码表而协同工作。从Unicode2.0开始,Unicode项目采用了与ISO 10646-1相同的字库和字码。 目前两个项目仍都存在,并独立地公布各自的标准。Unicode协会现在的最新版本是2005年的Unicode 4.1.0。ISO的最新标准是10646-3:2003。 UCS规定了怎么用多个字节表示各种文字。怎样传输这些编码,是由UTF(UCS Transformation Format)规范规定的,常见的UTF规范包括UTF-8、UTF-7、UTF-16。 IETF的RFC2781和RFC3629以RFC的一贯风格,清晰、明快又不失严谨地描述了UTF-16和UTF-8的编码方法。我总是记不得 IETF是Internet Engineering Task Force的缩写。但IETF负责维护的RFC是Internet上一切规范的基础。 3、UCS-2、UCS-4、BMP UCS 有两种格式:UCS-2和UCS-4。顾名思义,UCS-2就是用两个字节编码,UCS-4就是用4个字节(实际上只用了31位,最高位必须为0)编码。下面让我们做一些简单的数学游戏: UCS-2有2^16=65536个码位,UCS-4有2^31=2147483648个码位。 UCS-4根据最高位为0的最高字节分成2^7=128个group。每个group再根据次高字节分为256个plane。每个plane根据第 3个字节分为256行 (rows),每行包含256个cells。当然同一行的cells只是最后一个字节不同,其余都相同。 group 0的plane 0被称作Basic Multilingual Plane, 即BMP。或者说UCS-4中,高两个字节为0的码位被称作BMP。 将UCS-4的BMP去掉前面的两个零字节就得到了UCS-2。在UCS-2的两个字节前加上两个零字节,就得到了UCS-4的BMP。而目前的UCS-4规范中还没有任何字符被分配在BMP之外。 4、UTF编码 UTF-8就是以8位为单元对UCS进行编码。从UCS-2到UTF-8的编码方式如下: UCS-2编码(16进制) UTF-8 字节流(二进制) 0000 - 007F 0xxxxxxx 0080 - 07FF 110xxxxx 10xxxxxx 0800 - FFFF 1110xxxx 10xxxxxx 10xxxxxx 例如“汉”字的Unicode编码是6C49。6C49在0800-FFFF之间,所以肯定要用3字节模板了:1110xxxx 10xxxxxx 10xxxxxx。将6C49写成二进制是:0110 110001 001001,用这个比特流依次代替模板中的x,得到:11100110 10110001 10001001,即E6 B1 89。 读者可以用记事本测试一下我们的编码是否正确。 UTF-16 以16位为单元对UCS进行编码。对于小于0x10000的UCS码,UTF-16编码就等于UCS码对应的16位无符号整数。对于不小于0x10000的UCS码,定义了一个算法。不过由于实际使用的UCS2,或者UCS4的BMP必然小于0x10000,所以就目前而言,可以认为 UTF-16和UCS-2基本相同。但UCS-2只是一个编码方案,UTF-16却要用于实际的传输,所以就不得不考虑字节序的问题。 5、UTF的字节序和BOM UTF-8以字节为编码单元,没有字节序的问题。UTF-16以两个字节为编码单元,在解释一个UTF-16文本前,首先要弄清楚每个编码单元的字节序。例如收到一个“奎”的Unicode编码是594E,“乙”的Unicode编码是4E59。如果我们收到UTF-16字节流“594E”,那么这是 “奎”还是“乙”? Unicode规范中推荐的标记字节顺序的方法是BOM。BOM不是“Bill Of Material”的BOM表,而是Byte Order Mark。BOM是一个有点小聪明的想法: 在UCS编码中有一个叫做"ZERO WIDTH NO-BREAK SPACE"的字符,它的编码是FEFF。而FFFE在UCS中是不存在的字符,所以不应该出现在实际传输中。UCS规范建议我们在传输字节流前,先传输字符"ZERO WIDTH NO-BREAK SPACE"。 这样如果接收者收到FEFF,就表明这个字节流是Big-Endian的;如果收到FFFE,就表明这个字节流是Little-Endian的。因此字符"ZERO WIDTH NO-BREAK SPACE"又被称作BOM。 UTF-8不需要BOM来表明字节顺序,但可以用BOM来表明编码方式。字符"ZERO WIDTH NO-BREAK SPACE"的UTF-8编码是EF BB BF(读者可以用我们前面介绍的编码方法验证一下)。所以如果接收者收到以EF BB BF开头的字节流,就知道这是UTF-8编码了。 Windows就是使用BOM来标记文本文件的编码方式的。 6、进一步的参考资料 本文主要参考的资料是 "Short overview of ISO-IEC 10646 and Unicode" (http://www.nada.kth.se/i18n/ucs/unicode-iso10646-oview.html)。

下载文档,方便阅读与编辑

文档的实际排版效果,会与网站的显示效果略有不同!!

需要 5 金币 [ 分享文档获得金币 ]
0 人已下载

下载文档

相关文档