类型:Python,C++ & Qt4,Java,创建时间:Dec. 31, 2011, 4:52 p.m.
标题无“转载”即原创文章,版权所有。转载请注明来源:http://hgoldfish.com/blogs/article/29/。
如果非要评个计算机初学者最头晕概念的话,估计字符编码可以排到前十,比马尔可夫链之类的理论性概念都要晕。为什么呢?因为这东西课本上不会讲,也没有什么理论,只能靠自己体会出来。我比较笨一点,晕了好几年,到了今年终于明白了怎么一回事,现在记在这边,免得再忘了。
首先定义只在本文中出现的两个基础概念:
这两个概念是我生造的,主要是为了避免和现有的一大堆术语混起来。
文字串不能理解成字符串。倒是像圆这样的数学概念,比较抽象一些,只存在于头脑里。可以把文字串写在纸上,也可以记在计算机里,还可以读出来。字节组就是文字串在计算机中的表现形式。想一想,在纸上圆可以用一个公式来表达,也可以用一个图形来表达,还可以用文字描述它。文字串也一样,可以表述为多种字节组。众所周知,计算机只能处理数字,所以为了描述文字,最简单的方法就是把文字列出来,每个文字用一个数字表示。不同的国家有不同的方法来做这件事,比如美国人和英国人只有字母,还有一些符号,总共排了256个文字,叫做ascii码。中国的文字就比较多了 ,那个康熙大字典据说有40万,所以中国人排了gb2312标准,big5,gbk、还有gb18030,里面包含的文字总数各不相同。后面几个名词很熟悉吧。
显然,美国人的ascii码是不能表示中国文字的,因为它只能表示256个文字。虽然,中国的标准可以表示美国人的文字,但是却不能表示阿拉伯字符和其它一些像型文字。后来大家意识到这个问题是文化交流的障碍,于是就在一起制定了一个unicode标准。简而言之,unicode标准就是穷尽这个世界上所有的文字,给每个文字编一个数字。 和gb2312,gbk等一样,其实unicode也只是一种编码标准,只不过它能够表示所有的文字。从此,如果一个计算机系统想要支持多种语言文字,只要简单地支持unicode就可以了。在这种系统里,我们可以认为文字串就是unicocde字节组,unicode字节组就是文字串。
虽然解决了编码不能相互表示的大问题,但是还有一个麻烦没有解决。就像前文所说的,所谓编码就是给文字编一个数字。在每个编码里,数字与文字是一一对应的,但是在不同的编码标准里呢?不同的编码标准可能包含相同的文字,比如big5里的公和gbk里的公就是同一个文字,但是被编了不同的数字。这时候,同一个文字对应了不同的数字。再者,同一个数字是否代表着同一个文字呢?显然不是的,不然,两种编码标准就只有包含文字多少的区别了,或者每个文字在两个编码对应了相同的数字,那这还是两个编码标准吗?
我经常感叹,要是这个世界上只有unicode这种编码标准那该多好啊,我们就不用操心这些问题了。可惜事实不是这样子的,以前的标准仍然盛行,特别是windows仍然使用旧的标准。在中文简体的windows上,用记事本写的文本文件仍然是gbk编码的。想要在一个使用unicode的浏览器上显示出文字来仍然不行,不同的编码环境从文件里读取的数字不会变,但是却表示了不同的文字。怎么办呢?很容易想到的办法是读取文件内容的时候把gbk编码转成unicode编码。仔细想一想,这应该是没问题的,因为gbk包含的文字只是unicode包含的文字的一部分而已,从gbk转成unicode应该是没问题的。反之,从unicode转成gbk却不一定可以。
还有一种情况是两种编码标准差不多,只是每个文字在两种编码标准里对应的数字不一样。比如gbk和cjk,两种编码标准都包含了大量的中文简体文字和繁体文字,但是两种编码是不一样的。一个gbk的文本文件想拿到只支持cjk的环境里阅读要怎么办呢?也要在读取文件的时候从gbk编码转成cjk编码。目前一般采用先把gbk转成unicode,然后再从unicode转成cjk的间接做法。这可不可行呢?前面我们已经知道了,gbk转成unicode是可以的,但是unicode的文字转成cjk却不一定可以。不过这并不是什么大问题,因为如果真的不能从unicode转到cjk的话,说明那个文字cjk根本不能表示,直接从gbk转成cjk肯定也不可以。与直接从gbk转成cjk相比,间接的转码多了一个步骤,不过形式上比较统一,只要每种编码都编写一段与unicode相互转换的程序,就可以进行任意编码的相互转换。这个世界存在太多的编码标准,想要每两种可以转换的编码都可以相互转换需要的转换程序太多,试想想一个多边形的每两点相互连接。
再进一步地考虑,虽然编码是一样的,但是数字转换成字节组的方式可不可以有多种方式呢?答案是,可以的。最典型的是unicode编码。unicode编了很多个文字,个数总是在增加,目前(2008年7月)是100713个文字。把文字串转换成unicode字节组最简单的办法就是每个文字用三个字节来表示,因为100713这个数字最少需要三个字节才能表示。不过实际上通常采用的是utf-8方案。其中,最常使用的128个英文字母用一个字节来表示,而中文使用三个字节(注,我经常以为是两个字节,不过那是其它字符)来表示。还可以使用utf-16方案,其中英文和中文都使用两个字节来表示,而其它字符采用四个字节。还有一种utf-32方案,所有的文字都用四个字节来表示。大多数软件在内存中使用utf-16来表示文字,而在硬盘文件或者网络数据流中使用utf-8。前者字节数比较固定,比较容易处理,而后者比较节约空间。
讲到这里,相信大家对字符编码已经了解得差不多了吧。我们再来理论联系实际。
首先看看python对于字符编码的处理。应该说python是*nix派系语言里对字符编码处理得比较好的了。它内建了对unicode的支持。python2.x版本里有两种类型的字符串,一种称为str
类型,实际上可以理解为字节组。还有一种是unicode
类型,实际上是utf-16(utf-32)的字节组,我们可以理解为文字串。
"中文"
—> 这是str
类型,字节组u"中文"
—-> 这是unicode
类型,文字串从文件、终端、网络数据流或者其它外部输入中读取的都是str
类型字节组,而且向文件、终端、网络也只能输出str
类型的字节组。unicode
类型的文字串在输出到文件的过程中会自动转换成ascii编码,输出到终端的过程中会自动转换成系统默认的编码(简体中文windows为gbk)。str
类型与unicode
类型可以使用decode()
和encode()
两个方法互相转换,比如在简体中文windows的命令行界面里:
"中文".decode('gbk') == u"中文" u"中文".encode('gbk') == "中文"
想要从文件里读取gbk编码的数据然后使用utf-8编码发送到网络上可以这样:
buf=file(filename,"r").read() mysocket.send(buf.decode('gbk').encode('utf-8'))
很显然,unicode
文字串没有decode()
方法,而str
字节组没有encode()
方法。(在python 2.x里面,实际上是有的, python 3.x比较正常。)
值得注意的是,python经常会在输出的时候自动把unicode
文字串转换成str
字节组,而在字符串连接的时候把str
字节组转换成unicode
文字串。其中有些规律我还不大懂,哪位大虾指导一下。
在以后版本的python里,表示方法略有不同:
"中文" # 这是一个unicode字符串 b"中文" # 这是一个str(python3k叫做bytes)类型的字节组
接下来再以java为例。与python相比,java对于unicode的支持更胜一筹,因为存储在java程序文件或者数据文件里的字符串都是以utf-16保存的,并且输入输出时,java能做更多的自动转换(比如列出一个文件夹里所有的文件)。不过基本原理跟python是一样的,它也有两种类型,一种是byte[]类型,这是字节组。还有是String类型,这是unicode文字串。它们也是可以相互转换的:
String("中文").getBytes("utf-8")
—-> 将unicode文字串转成utf-8字节组new String(new byte[]{228, 184, 173, 230, 150, 135},"utf-8")
—–>将utf-8字节组转成unicode文字串java还用输入输出专门提供了自动转换编码的类。InputStream
和OutputStream
专门用于输入输出byte[]
字节组,而Reader
和Writer
专门用于输入输出unicode文字串。
最后,再说明一下Qt对于字符编码的处理。 c和c++对于unicode的支持是比较差,幸好qt为此下了番苦功,整体来说还过得去。qt对于字符编码的处理基本上与java类型。QByteArray
(或者char[]
)类型是字节组,而QString
是unicode文字串。它们之间的关系是这样的:
char[] data=char[6]{228, 184, 173, 230, 150, 135}; //将utf-8字节组转成unicode文字串 QTextCodec::codecForName("utf-8").toUnicode(data,6) // 将unicode文字串转成utf-8字节组 QTextCodec::codecForname("utf-8").fromUnicode(QString("中文"))
qt会自动地将c的char[]
转换成unicode文字串。比如上文的QString("中文")
。还有tr()
函数,以及C++的隐式类型转换。自动转换使用的默认编码通常在QApplication构造后进行初始化,比如:
QTextCodec::setCodecForCStrings(QTextCodec::codecForName("gbk")); //与文件的编码同 QTextCodec::setCodecForTr(QTextCodec::codecForName("gbk")); //与文件的编码同
看起来很麻烦的样子,像java程序里"中文"
就可以直接表示unicode文字串了,而qt还需要tr()转换一下。不过,幸好从utf-8字节组转换成unicode文字串还是比较简单的:
QString::fromUtf8(data); // 将utf-8字节组转成unicode文字串 tr("中文").toUtf8(); // 将unicode文字串转成utf-8字节组
如有不对,还请大牛们指正
顺便发一个将一个文件夹下所有的gbk文件改成utf-8的脚本吧:
# -*- encoding:utf-8 -*- import os, sys try: path=sys.argv[1] except: path=os.getcwd() def gbkToUtf8(filename): data=open(filename, "rb").read() try: data=data.decode("gbk").encode("utf-8") print "success: ", filename except UnicodeError as e: print "failed: ", filename open(filename, "wb").write(data) for root, dirs, filenames in os.walk(path): for filename in filenames: if filename.endswith((".cpp", ".hpp", ".c", ".h", ".cc")): gbkToUtf8(os.path.join(root, filename))
标题无“转载”即原创文章,版权所有。转载请注明来源:http://hgoldfish.com/blogs/article/29/。
暂时还没有任何评论。