Python, Qt, Java的字符编码


类型:Python,C++ & Qt4,Java,创建时间:Dec. 31, 2011, 4:52 p.m.

标题无“转载”即原创文章,版权所有。转载请注明来源:http://hgoldfish.com/blogs/article/29/。

如果非要评个计算机初学者最头晕概念的话,估计字符编码可以排到前十,比马尔可夫链之类的理论性概念都要晕。为什么呢?因为这东西课本上不会讲,也没有什么理论,只能靠自己体会出来。我比较笨一点,晕了好几年,到了今年终于明白了怎么一回事,现在记在这边,免得再忘了。

首先定义只在本文中出现的两个基础概念:

  • 字节组。每8个位(bit)是为一个字节,多个字节成为一个字节组。
  • 文字串。现实语言的最小单位称为文字。一个英文字母就是一个文字,一个汉字也是一个文字,一个空格也是文字。

这两个概念是我生造的,主要是为了避免和现有的一大堆术语混起来。

文字串不能理解成字符串。倒是像圆这样的数学概念,比较抽象一些,只存在于头脑里。可以把文字串写在纸上,也可以记在计算机里,还可以读出来。字节组就是文字串在计算机中的表现形式。想一想,在纸上圆可以用一个公式来表达,也可以用一个图形来表达,还可以用文字描述它。文字串也一样,可以表述为多种字节组。众所周知,计算机只能处理数字,所以为了描述文字,最简单的方法就是把文字列出来,每个文字用一个数字表示。不同的国家有不同的方法来做这件事,比如美国人和英国人只有字母,还有一些符号,总共排了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还用输入输出专门提供了自动转换编码的类。InputStreamOutputStream专门用于输入输出byte[]字节组,而ReaderWriter专门用于输入输出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/。


暂时还没有任何评论。


何不来发表一下您对本文的看法(使用Markdown语法,分段空两行):