要彻底解决乱码问题,就要先将那些混杂不清的概念区分开来,最常混淆的就是字符集和字符编码,下面将详细区分介绍几个容易混淆的概念。
字符(Character):
字符是具有语义值的最小文本单元,是各种文字和符号的总称,包括各个国家文字、标点符号、图形符号、数字等。比如字母'a',符号'@',汉字'中'等,每个字符都代表某种含义。
但很多人都有一个误解,普遍认为英文字母和特殊符号在计算机中占一个字节,而汉字占两个字节。大错特错!一个符号在计算机中所占的字节与具体字符编码有关。上述情况在使用“GB2312”的字符编码时成立,而使用“UTF-8”就不成立。字符编码会在后面详细介绍。所以在“字符”这个术语的定义中并没有定义具体占的字节数。
大家不要在不知道使用哪种特定的字符编码的情况下就说字符占几个字节,这样是不严谨的。
字符编号:
用来标识某一字符的号码,具有不重复性,且在不同的字符集中,某个字符的字符编号不一定相同。比如在GBK字符集中“中”字的字符编号是D6D0(十六进制),而在Unicode字符集中,“中”字的字符编号是4E2D。
字符集(Character Set):
左图为ISO9959-1字符集
平常大家口中相传的字符集,准确来说应该叫“编号字符集”(后文提到的“字符集”都代表“编号字符集”)。什么叫“编号字符集”呢?就是用上文提到的“字符编号”来标识某一字符集中的每一个字符后产生的字符集合。那为什么要给字符编号(注意:与字符编码不同!)呢?其实很简单,如果不给你起名字,别人如何才能找到你呢?编号就是为了方便找到,或者说定位某个字符。
“编号字符集”也简称字符集,它是多个字符的集合,例如ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)字符集,定义了数字、英文字母、特殊符号等一系列字符。字符集有很多种,常见的字符集有:ASCII,ISO8859-1,GB2312,Unicode等等。每个字符集包含的字符个数也不一定相同,比如ASCII字符集收录了128个字符,而GB2312收录了7445个字符。
很多人会把字符集和字符编码混为一谈,比如常说的”UTF-8字符集”,“GBK的编码方式”等。 其实这些说法都不严谨。但如果在Unicode字符集还没有产生的时候,说ASCII、GB2312,GBK等,是一种字符集或者说是一种字符编码都是正确的。因为Unicode还没诞生时,各个国家和地区在制定编码标准的时候,“字符集”和“字符编码”一般都是同时制定的(或者这样说会更好理解:这些字符集在制定时没有考虑到扩展性,所以一种字符集只制定了一种字符编码,所以默认使用字符集的名字来代表字符编码的名字。)。因此,平常我们所说的“字符集”,除了有“字符的集合”这层含义外,同时也包含了“字符编码”的含义。而Unicode字符集诞生后,因为吸取了之前字符集的教训,从而为这一种字符集制定了多套字符编码。(具体字符集和对应字符编码的介绍下篇文章会详细讲述,这里多说无益)所以这时再把字符集和字符编码混为一谈就不合适了。
所以大家只要记住,字符集和字符编码不是一个概念,字符集只是用字符编号标识的一组字符的集合。而字符编码是规定了字符如何转化成二进制字节流的映射规则。
码点(code point)(如果看不懂下面的解释不用深究,直接跳过):
编号空间中的一个位置(Position)称为码点(Code Point代码点)或码位(Code Position代码位)。一个字符占用的码点所在的坐标(非负整数值对)或所代表的非负整数,就是该字符的编号,又称之为码点值(即码点编号)。
不过,严格来讲,字符编号并不完全等同于码点编号(即码点值)。因为事实上,由于某些特殊的原因,编号字符集里的码点数量要大于抽象字符表ACR中的字符数量。在编号字符集中,除了字符码点之外,还存在着非字符码点和保留码点,所以字符编号不如码点编号准确;但若对字符码点的码点编号称之为字符编号,倒也更为直接。
编码(Encode):
专业说法:是信息从一种形式或格式转换为另一种形式或格式的过程,比如用预先规定的方法将字符(文字、数字、符号等)、图像、声音或其它对象转换成规定的电脉冲信号或二进制数字。通俗说法:将字符转换成二进制字节流。
解码(Decode):
专业说法:用预先规定的方法将电脉冲信号或二进制字节流转换成字符(文字、数字、符号等)、图像、声音或其它对象。通俗说法:将二进制字节流转换为字符。
字符编码(Character Encoding):
专业说法:是把字符集中的字符按一定格式(形式、方式)编码为某指定集合中某一对象(比如由0和1两个数字所组成的位串模式、由0~9十个数字所组成的自然数序列、电脉冲等)的过程,亦即在字符集与指定集合两者之间建立一个对应关系(映射关系)的过程。这是信息处理的一项基础技术。
通俗说法:是字符集和实际存储的二进制字节流之间的转换关系,是定义在字符集上的映射规则,是将字符集中的字符编号映射为字节流的一种具体实现方案。例如ASCII字符编码规定使用单字节中低位的7个比特去编码所有的字符。例如'A'的编号是65,用单学节表示就是0x41,因此写入存储设备的时候就是'01000001'。GB2312编码则是将区位码(区位码下篇博客会解释)中的区码和位码的分别加上0xA0(十进制的160)的偏移(之所以要加上这样的偏移,主要是为了和ASCII码兼容),例如“中”字的区位码是5448,十六进制是0x3630。区码和位码分别加上0xA0的偏移之后就得到0xD6D0,转换成二进制后得'1101011011010000',这就是“中”字用GB2312编码后的二进制字节流。
代码页(Code Page)(可跳过):
代码页也称为“内码表”。一种字符编码具体形式。早期字符相对少,因此通常会使用类似表格的形式将字符直接映射为字节流,然后通过查表的方式来实现字符的编解码。现代操作系统沿用了这种方式,例Windows使用936代码页,Mac系统使用EUC-CN代码页实现GBK字符集的编码。