首页 > 代码库 > 关于字符集的学习笔记

关于字符集的学习笔记

大家知道,在计算机中,数据都是以二进制的形式来表示的,只有 0 跟 1。而我们在电脑屏幕上看到的能够被我们所理解的这些都是字符,不管是中文也好,英文也好,它们被存储到计算机内部的时候,都会被转换成二进制。

那用什么样的二进制来显示什么样的字符呢,这就是由人们来决定了,这就是所谓的编码。

也正因为此,出现了各种各样的编码方式,比如ASCII,UNICODE,还有和我们汉字相关的GB2312和GBK等。

ASCII

ASCII码是大家最熟悉的一种编码方式了,是在上个世纪60年代(1961年)由美国制定的一套标准,原来是美国的国家标准,后来被ISO定为国际标准,在今天,所有的机器上都支持ASCII编码集。

ASCII码基本上是对英文字母和二进制的编码转换,它共由7位二进制位表示,因此其一共能表示128个字符,其中0~31 是控制字符(不能打印出来),还有96个可以打印出来的字符,包含数字,大小写英文字母和一些标点符号等。

但是由于计算机存储的基本处理单位为字节(Byte),也就是8位,所以一般会以一个字节来存储ASCII码,那么最高位就会置0。


从上表,我们可以看到,ASCII码仅支持英文,如果全世界通行的语言就只有英文,那就简单了,也就不会有那么字符编码格式了,但是很可惜,事实上不是这样的。

世界的多元化发展,造就了多姿多彩的语言,比如现在我们大家都看到的汉字,对吧。

每一个汉字就像每一片树叶,都是独一无二的。那么仅仅有128种状态的ASCII码表,是没有办法来满足汉字的表达的。

于是有一些西欧国家,就决定利用ASCII码中的闲置的最高位,这样的,利用8位编码,它们能够表示256种状态,也就是说他们能够利用扩展的ASCII码来表示256个字符。

对于这一些以拉丁字母为主的西欧国家,256个字符是足够的。

但是却存在一个问题,不同的国家,原来的0-127个ASCII码,大家都保持不变,还是统一的,但是在128-255这一段的编码都是按照自己国家的语言来表示,就会出现这样一个问题:

同样的编码,在不同的国家,显示为不同的字符。

在互联网还不普及,全球化交流没那么频繁的时候,这种缺陷还不明显,但是在当今这个时代显然是不能拉受的。

因为计算机显示任何一种字符,都必须要先知道对应的编码方式,否则就会显示乱码了。

另外,对于我们亚洲国家,尤其是汉字来说,256个字符还是远远不够的!


ANSI编码和MBCS

于是为了显示本国的语言,不同的国家和不同的地区分别制定了不同的标准,于是就产生了诸如GB2312(简体中文字符集),BIG5(繁体中文字符集)等各种不同的编码标准。

由于汉字远远不只256个字符,所以这些编码方式都采用了2字节的编码方式,在Windows系统上一般称之为ANSI编码(Windows-1252),也称为多字节字符集(MBCS, Multi-Bytes Character Set)。

这是针对Windows系统的,在中文Windows操作系统下,ANSI编码就是GB2312编码,而在日文操作系统下,ANSI编码就是JIS编码。

但是不同的ANSI编码是没有办法互相转换的,谁也不认识谁。

于是我们又回到了之前的一个问题,在当今这个全球化的时代,我们必须有一个统一的字符集能够包含所有的字符。


Unicode

Unicode,我想应该是universal code的合拼,它表明就是整个宇宙所有的编码都会包含在这里面,当然,目前也就是地球而已,不包含火星文。


Unicode将目前世界上通用的所有的字符都收纳了进来,为每一个字符分配了一个唯一的Unicode,比如U+4E00就是汉字的“一”,通过UltraEdit等工具,我们可看看其保存为UTF-16时的编码格式如下:


我们是将文件保存为UTF-16的编码格式的,那么UTF-16和Unicode有什么关系呢,上面内容显示的“FF FE”又是表示什么意思呢?

在这里,我们首先要明确一点, 那就是Unicode只是一个字符集,它只定义了在Unicode中,一个符号其对应的16进制是什么,比如上面汉字的“一”,其对应的Unicode就是4E00,除此之外,它不干其他事情。

为什么Unicode不定义字符的存储结构呢?

我觉得主要原因在于资源的存储和传输的问题,比如对于ASCII码里面的英文字母,它们当然也包含在Unicode中,如果Unicode统一规定了一个字符要用多少个字节来存储,那么很显然肯定不只能用一个字节,因为除了英文字母,

还有其他语言字符。那如果用2个或者更多个字节,ASCII码又用不了这么多,每次存储都要用好多的00000在前面,这多浪费资源,传输起来也会有效率的问题。

很显然,这就决定了会有多种Unicode的存储方式,比如我们上面讲到的UTF-16,其他的还有我们经常用到的UTF-8,还有UTF-32等。

偶尔,我们还会看到UCS-2和UCS-4,它们跟Unicode有什么关系呢?其实Unicode字符集,就是叫做Unicode字符集,英文也就是 Unicode Character Set,即UCS,那么UCS-2就是表示用两个字节,也就是16位来表示的Unicode了。

但是要注意一点,基于维基百科,UTF-16是用来取代UCS-2的。

UCS-2表示的Unicode中从U+0000到U+FFFF这段范围的字符,但是在这段字符中,U+D800到U+DFFF是作为无字符来保留的,而UTF-16,则是利用了这段保留位置保存了位于U+010000到U+10FFFF的字符集。

所以,虽然两者都是只能表示2^16个字符,但其范围是不一致的。

至于UCS-4,就是UTF-32是一样的了。

说到这里,相信大家,也就清楚了,其实UTF-16,也就是一种对Unicode的实现方式,只是它对于Unicode中U+0000到U+FFFF这段字符的存储格式的实现。

而由于都是两个字节,所以基本上Unicode的值是什么,UTF-16的实现也就是什么值,以致于很多情况下人们把Unicode跟UTF-16都划上等号了,其实,它们一个是定义,一个是实现的存储方式。

因为正好是两个字节,那么计算机到底将哪个字节保存在前,哪个字节保存在后呢?都是可以的,这就涉及到一个字节序的问题了。

字节序

字节序也称之为Byte Order Mark,也就BOM,就是上面保存形式中的FF FE了,这是Unicode规范中的推荐方法。

Unicode字符中,定义了这样一个字符串,U+FEFF,叫做“zero-width non-breaking space”。在存储字符前,添加这样一个BOM,来表明编码的顺序,告诉CPU应该先存高字节,还是低字节。

Little Endian: 一般叫小尾,添加FF FE,表明是反过来存储,先存低字节(后面的字节),再存高字节(前面的字节),比如汉字的“一”,其Unicode值为U+4E00,则如果选择UTF-16(Little Endian)在存储方式,则就会是上图中的存储了。

Big Endian:一般叫大尾,会在字符前面添加FE FF,正好反过来,按顺序先保存高字节,再保存低字节,存储为FE FF 4E 00了,如下图所示。


上面所述的,一般就是UTF-16的存储方式,但其限定了固定两个字节的存储方式,而UTF-8,则是以一种变长的存储方式,慢慢地变成互联网上最流行的一种字符存储和传输方式。

UTF-8

UTF-8,可以使用1-4个字节来表示一个符号,其编码规则如下:

对于单字节的符号,第一位设为0,后面的七位设置为其Unicode值,也即是ASCII码了。

对于n(n > 1)字节的的符号,第一个字节的前n位设置为1,第n+1位设置为0,而接下来每个字节的前两位固定为10,之后将该符号对应的unicode值的二进制位按顺序填充剩下的位数。

具体形式如下:

Unicode符号范围 | UTF-8编码方式
(十六进制) | (二进制)
--------------------+---------------------------------------------
0000 0000-0000 007F | 0xxxxxxx
0000 0080-0000 07FF | 110xxxxx 10xxxxxx
0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx


根据这个表格,我们可以看看汉字“一”,变成UTF-8编码的值为多少。

其Unicode值为4E00,其范围在0000 0800 - 0000 FFFF这个区间,可见其是要用三个字节来编码的,其二进制为0100 1110 0000 0000,则可知其UTF-8的二进制为1110 0100 1011 1000 1000 0000,转化为16进制为 E4 B8 80 00,接下来让我们将上面的“一”字保存成UTF-8格式吧


结果如上图所示,那么EF BB BF又是什么呢?

UTF-8是以8位为单元对Unicode进行编码的,所以其是以字节为单位来编码的,那么就不存在什么字节序的问题了,而EF BB BF其实是对FE FF的编码来的。

既然不存在字节序的问题了,为什么还要在前面添加这个BOM呢?

这是因为,可以利用这个BOM来表明编码方式,当接收到EF BB BF开头的字符串的时候,我们就知道后面的字符串是以UTF-8编码的。

好了,结束!