字符编码那点事儿

那时,天下人的口音、言语,都是一样。他们往东边迁移的时候,在示拿地遇见一片平原,就住在那里。他们彼此商量说:“来吧!我们要作砖,把砖烧透了。”他们就拿砖当石头,又拿石漆当灰泥。他们说:“来吧!我们要建造一座城和一座塔,塔顶通天,为要传扬我们的名,免得我们分散在全地上。”耶和华降临,要看看世人所建造的城和塔。耶和华说:“看哪!他们成为一样的人民,都是一样的言语,如今既作起这事来,以后他们所要作的事,就没有不成就的了。我们下去,在那里变乱他们的口音,使他们的言语彼此不通。”于是,耶和华使他们从那里分散在全地上;他们就停工不造那城了。因为耶和华在那里变乱天下人的言语,使众人分散在全地上,所以那城名叫巴别。

——《创世记》 

字符、字符集、字符编码

先说字符,字符大家都很清楚了,英文叫做character,通常的简写叫做char,字符加上一种字体(Font),就可以得到字型(Glyph),字型就是我们眼前所见之物了。 

字符用来在计算机领域表示语言和文字,世间有无穷多字符,如果沟通的双方要互相明白,就必须使用相同的字符集。字符集会规定一种或者几种用计算机字节表示字符的方法,这个方法就叫做字符编码。 

ASCII 

ASCII恐怕是最最著名的字符集了,至今为止还有人将ASCII误认为是计算机中表示字符的方法的代称,在任何编程系统中都会问"如何获取某一字符的ASCII码"。ASCII中共有127个字符,主要包含大小写英文字母、标点和一些控制符号。ASCII既是字符集,又是字符编码,ASCII的每个字符对应着0-127中的一个整数,这个整数称为字符的ASCII码。例如,字符'a'的ASCII码是97。

ASCII在计算机发展初期是非常NB的,这个时期主要是英语国家在使用计算机,而对当时的少数非英语国家用户而言,英语根本不成为门槛,ASCII编码可以放在一个Byte之中,这通常是计算机最小的空间单位。

哦对了,ASCII后来被国际标准化组织搞成了ISO/IEC 646,算是有了正式名分。

ASCII兼容编码 

因为ASCII对于英语之外的文字完全没有考虑, 所以显然没法满足全球人民日益增长的字符使用需要,而由于ASCII编码被广泛使用,所以又没法完完全全地重新发明一套新的字符集和编码方式。

注意到ASCII只占用了0-127,但是一个字节其实可以表示256个数字,于是128到255或者说-128到-1这些值被一些人开始盯上了。于是一些ASCII兼容编码开始出现了,其中我朝人民最为熟悉的就是GB2312了,unicode的UTF8编码方式也是ASCII兼容的,这些后文再详细分说。  

ASCII兼容是向下兼容,即某种编码方式不需要经过任何转换接受任何以ASCII编码的文本为合法的,并且字符与当做ASCII编码时是完全一致的。

几乎所有的ASCII兼容编码方式都是变长编码,因为每个ASCII字符只占一个字节。 

GB2312、GBK、GB18030

ASCII字符集里面不包括中文,那么显然这对于我国人民的感情来说是难以接受的,于是党和各级人民政府当然不会允许这种事情发生。在倪光南院士等人的关怀下,1980年具有独立自主知识产权的GB2312横空出世了。

1995年,在社会主义伟大旗帜下,有关部门组织一些专家对GB2312做了一些扩充,形成了GBK编码,也就是GB13000标准。

2000年,为了满足人民日益增长的字符使用需要,我国又制定了GB18030标准。

GB系列的编码还有一个称呼叫做区位码。

ANSI 

关于ANSI这种说不上是编码方式还是字符集的充满狗血故事的概念,还真是不太好讲。好吧首先ANSI的意思是American National Standards Institute,美国国家标准学会,嗯,但是很遗憾美国国家标准学会从来没有制定过这么一个标准,也不可能制定出来(好吧很快你就可以知道为什么了)。

最初这个东西如何出现,现在已经不可考证,但是毫无疑问至今windows的记事本保存选项里面仍然有这么一个叫做ANSI的选项。为什么说ANSI其实并不是一种字符集,也不是一个编码呢?因为实际上ANSI会随着操作系统的区域和语言设置而变化!当你将非unicode默认语言选成中文时,这个选项保存出来的文件赫然就是GB2312!对的!我认为ANSI这个词乃是米帝国主义亡我之心不死,妄图将伟大社会主义字符编码标准GB2312划入米国之手啊!

好吧,认真地说, ANSI这个概念颇为脑残,它的实际意思是遵循本地化设置的编码方式,但是却不伦不类地扯上了米国国家标准学会。 

Unicode

讲字符编码一定不能漏了Unicode了,现在Unicode确实不负uni之名,是最有机会达成大统一重造巴别塔的一个字符集。Unicode的最新版本是6.0。

Unicode是一个字符集而不是一个编码方式,Unicode中每个字符对应一个code point(中译为码点,觉得不喜欢这个翻译) 。code point是一个0x00000到0x10FFFF之间的正整数。因此Unicode的字符都被记为U+XXXX,XXXX就是code point的16进制表示了。

Unicode还把code point分成17个code plane,其中主要使用的是0,1,2,3四个平面,也就是U+0000-U+FFFF,U+10000-U+1FFFF,U+20000-U+2FFFF,U+30000-U+3FFFF这四个code point的集合。

Unicode本身是抽象的,它发布的时候会带一个参考字型但是实际上它把字型的决定权交给了字体,所以code point就是字符的唯一标识了。

UTF

unicode本身并非编码方式,所以unicode需要编码方式配合,常见的unicode编码方式就是UTF。UTF-X的数字X就表示最少用几个bit来表示一个字符。

常见的表示方式有UTF-8、UTF-16、UTF-32、UTF-7、UTF-24......

Unicode的 code plane共只有17个,所以UTF-32已经是定长编码了,它能表示的范围是0x00000000到0xFFFFFFFF,这显然远远超过现在Unicode的code point范围,所以短期内可以认为UTF-32是定长编码。定长编码的好处是,获取一个字符串的长度可以直接根据它占用的内存计算出,所以UTF家族中只有UTF-32可以享受到了。

UTF-8是最常用的UTF编码,特别地,它还是ASCII兼容的。

值得注意的是,这些编码里面,UTF-16使用甚广,因为unicode1.0发布的时候, UTF-16还可以认为是定长编码,于是一些倒霉孩子比如Java就把UTF-16当成自己的字符串在内存中存储使用的编码,于是搞出来Char类型其实并不是每个表示一个字符,而是表示一个UTF-16 code unit这样的人间惨剧。

UTF-8对英文文本来说是非常合适的,但是中文每个文字要占三个字节,相比之下,UTF-16中的中文则是只占2个字节,所以当你的系统对存储容量或者网络流量敏感的时候,应该根据你的文本主要组成方式选择UTF恰当的编码。

大端和小端

对于UTF-16、UTF-32这种东西而言,还有一个很现实的问题,它们的code unit都是一个正整数,而计算机系统的最小单位是字节,字节表示正整数是有高位低位顺序之分的,于是我们就有了大端小端的事情,相信用过window记事本的同学都有印象,里面有两个选项unicode big endian和unicode,实际上这里unicode指的就是UTF-16编码。 

BOM

BOM又叫做Byte Order Mark,起因是我们的unicode里面规定了一个神奇的字符零宽空格U+FEFF,这个字符好啊,它明明是个字符,还显示不出来,于是一些程序员大爷想出了个nb的办法,约定所有的UTF数据流,都先吐出一个U+FEFF来,反正又不会显示出来,根据这个U+FEFF的表示方法,就可以判断数据流所使用的UTF编码了。比如说,收到三个字节EF BB BF的话,就可以断定来的编码是UTF-8了。

这个NB办法后来也被推广到其它编码上(嗯,比如GB就不行,跟BOM冲突,打开记事本输入"联通",然后保存为ANSI,再打开看看),也渐渐有了称呼叫做BOM。但是很遗憾,不少人不懂规矩,不会在数据源前面加BOM,于是天下间的文本,又被分为了有BOM和无BOM两种...... 

UCS

很多人说Unicode的标准是ISO16406,实际上这话不对,UCS才是。这中间有一段非常蛋疼的历史,unicode和ucs是分别由两个不同组织编写和发布的不同标准,但是因为二者在很早些时候就达成共识,一直紧密合作,始终默契地保持两份标准的一致,所以其实可以认为Unicode就是UCS了。

然而跟Unicode不同,UCS本身还规定了编码方式,主要有两种:UCS-2和UCS-4,这里的2和4表示最少用几个byte来表示一个字符。UCS-4也是定长编码,它跟UTF-32完全一致,所以二者只是理论上是两种编码而已。

值得注意的是,UCS-2与UTF-16是不完全一致的,UCS-2也是一种定长编码,code point超出0xFFFF的字符怎么办呢?UCS-2......它表示不了这些字符!

ISO-8859

实际上,制定了自己的编码标准的不仅仅是我们伟大的天朝,欧州国家也制定了一些自己的编码规范,它们都是针对特定地区的编码,不同语言使用不同字符集,所以互相之间不能混排,简单地说,就是跟GB2312一路货色,这些东西也就是前面提到过的ANSI编码了。

ISO-8859是一组规范,而不是一个规范,ISO-8859-1大家一定经常在各处见到,它表示西欧字符集。ISO-8859家族现在一共有15个,序号是从1排到16,除了12之外,关于为啥没有12有些不同说法,又说原本是要给加入塞尔特语族的Latin-7,也有说是留给印度天体梵文的,总之就是因为一些奇怪的原因跳过去了。

很遗憾我朝大佬对于ISO毫无兴趣(也许ISO9001是例外?),因此GB始终是GB,没能加入ISO当中,不过GB基本可以认为是跟ISO-8859同一体系的一种编码。

结语

对于字符和编码的这点事,我自己蛋疼了很久,才渐渐理解了其中错综复杂的关系,这篇文章信息量不大,不过是个感性理解,希望对一些人会有帮助吧。 

 

posted @ 2012-01-27 23:42 winter-cn 阅读(...) 评论(...) 编辑 收藏