注:因为两边同步的麻烦,更多更改及调整可参考个人网站:xiaogd.net 上的字符集编码与乱码系列,已将字符集编码系列与乱码探源系列合并,更新及勘误等再也不更新到这边。html
在深刻研究字符集编码,简称编码以前,咱们先引入一个概念:编号(code),引入它是为了更好地与编码(encode)相区分。网络
若是你对Unicode有深刻了解,你也许已经意识到了Unicode中码点(code point)扮演的正是编号的角色。相似的还有GB系列中所谓的区位码。ide
其实叫什么并不重要,爱咋咋地,我并不关心。但乱叫容易叫混了,好比把码点也叫成Unicode编码,这里先把这些纳入到编号概念。为区别起见,用黑色加粗的编码特指字符集编码。网站
到了后面你甚至会为字符集编码的边界在哪而困惑,为它的准肯定义而纠结,不过到那时你已经属于”可贵糊涂“了,编号这一律念你也能够把它丢到爪哇国去了。编码
编号是什么?spa
编号能够看做是字符与编码中间的一个抽象层,过渡层:.net
广义上说,编号也可当作是某种编码;代理
狭义上说,编码也可视做为某种编号。code
图:字符<-->编号<-->编码orm
编号与编码的主要区别在于编号不涉及具体使用多少字节来表示、是用定长仍是变长方案等细节问题。编号仅仅是一个抽象的概念,是把字符数字化的一个过程。
在进一步比较编号与编码以前,咱们先看看编号是如何来的。
数学意义上的集合(Set):一组不重复的,无序的的元素。
通常意义上的字符集:不重复的,带编号的有序的的字符集合。
图:数学意义上的字符集对比实际的字符集
字符被整理出来以后每每数量众多,一般是置于表格之中。出于统计方面的缘由,人们一般用一个数字来编号。表格自己就是一种有序的暗含了编号的形式,哪怕你没有明确地为其编号,人们拿到这个表格也会说:”嘿,第一个字符是XXX!“这里的一不就是编号吗?
能够把整理的工做视做是由一群彻底不懂计算机的语言文字学家来完成的,他们甚至连字节是什么都没据说过。所谓编号是抽象的就是说它仅仅是一个数字而已。
这里不打算去讨论哲学意义上的等同,你可能会碰到有些字符彼此间长得很是像,在有了编号后,咱们能够简单地说,只要是编号不一样的两个字符就是不重复的。
图:一些很类似的字符,图片来自http://wiki.secondlife.com/wiki/Unicode_In_5_Minutes
这里的U+[XX]XXXX是码点的表示形式,X表明一个十六制数字,能够有4-6位,不足4位前补0补足4位,超过则按是几位就是几位。具体范围是U+0000~U+10FFFF。
注意不要将它与UTF-16的编码搞混了,尤为是那些4位的码点,虽然很类似,但一个是编号(code),一个是编码(encode),处在不一样的概念层次。
关于码点及Unicode的更详细介绍,可见字符集与编码(四)——Unicode
咱们能够看到至少有三个码点上的a是很是像的。其中的U+FF41中所谓的FULLWIDTH(全宽)其实就是全角,也即两个半角宽度,我想咱们对此都很熟悉。
当初GB2312出来时,仗着本身编码空间大,把ASCII里那些字母符号之类的又重复弄出一套所谓的全角版原本,后来Unicode又把这些又再收罗了过去。
不必定!它能够是数字对,或者你叫它复数,二元数啥的,随便你。但只要它是离散可数量子化的,它天然也能够转换成惟一的一个数字。参见前面图中的二维区位编号,咱们用数字对(1, 1)编号“h”这个字符。(1, 1)能够简单转换成11,而后能够进一步映射到从0或者1开始的编号。
有序不意味着连续。这里须要说明的一点是,从前面的叙述来看,编号早于编码,但实际状况是人们一般是一块儿考虑这二者的,编号反过来会受到编码考虑的影响,这样作只是为了让从编号到编码的映射或者叫转换更加方便。这些影响包括:
若是按平常习惯,编号一般应该从1开始,受编码影响,编号也从0开始。
编号写成十进制是更天然的方式,受编码影响,编号一般也以十六进制形式来书写,并写成固定的位数,不够时就在前面填充0,好比把48写成0048;又好比:U+1D11E就是一个一个五位的编号。
为了之后的扩展方便,编码经常会跳过某些码位,甚至会保留大片的区域未定义或做保留用途。好比Unicode有所谓的代理区(surrogate area),后续咱们会进一步了解。编号所以也跳过这些。(其实到了后面你会发现,究竟谁影响谁还真很差说!不过等你明白之时这些已经不重要了。。。)
总之一句话就是让映射规则尽量简单。
图:编号最终与编码几乎同样的一种可能情形,为简单起见,使用十进制。
你可能会说,那这样它们还有什么区别?你的确能够把编号也说成是编码。
但事情并不老是这样,这种类似性确实迷惑了不少人,特别是Unicode,不少人把码点说成是Unicode编码,这种说法自己并无错,这取决于你如何定义编码,但他们是否意识码点仅仅一种抽象的编码呢?为了区别,Unicode把最终的具体编码称为UTF(Unicode Transformation Format),即所谓的Unicode转换格式。
所谓转换,其实就是把抽象的数字映射到具体的,最终的编码上来。
把一个字符编码到一个数字(不涉及每一个数字用几个字节表示,是用定长仍是变长表示等具体细节)
即UTF,把抽象编码层面的数字编码成最终的存储形式,须要明确是用定长仍是变长;定长的话定几个字节;用变长的话有哪几种字节长度,具体如何去实现等等。(注:在上一层面,字符与数字已经实现一一对应,对数字编码实质就是对字符编码)
这里所谓抽象与具体,以U+0061(ascii字母a)为例,十六进制的0061也就是十进制的97,所谓抽象,也便是用97这个数字表示a;所谓具体,就是在计算机的底层到底怎么表示的问题。即使是表示一个整数,你也面临着究竟是用byte,short,仍是int,long来表示的问题,这就是具体。更具体到编码,你还面临是用定长仍是变长等抉择。以UTF-32为例,本质上与一个四字节的unsigned int(无符号整型)没什么区别。
编码是一个很是宽泛的概念!虽然咱们前面一直用编码特指字符集编码,但这只是一种狭义的理解,广义的理解则有不少:
文字是对声音的编码
照相机,摄像机把光信号编码成图像及视频
咱们还常常能看到条形码,二维码,这些都是编码
在《编码:隐匿在计算机软硬件背后的语言》(Code: The Hidden Language of Computer Hardware and Software)一书中,参见豆瓣读书http://book.douban.com/subject/4822685/,做者提到了莫尔斯电码(Morse Code)
以及布莱叶盲文(Braille Code),这些都是编码的例子。
在《信息简史》( The Information: A History, A Theory, A Flood)一书中,参见豆瓣读书http://book.douban.com/subject/25752043/,做者提到了一个有趣的“会说话的鼓”的故事,非洲的一些部落成员之间能够用鼓声来交流很是复杂的讯息,在这里就是用鼓声来编码信息。
电影《修女传》中有这样的情形,当修女们还坐着船在刚果河上行进时,船上的鼓手们就提早用鼓声告诉远方的目的地村庄,“来了一位美丽的修女”(由奥黛丽·赫本(Audrey Hepburn)主演)。声音的速度毕竟比船快!
回到咱们的字符集的例子,虽然咱们倾向于认为编码就是指最终存储的形式,好比写入文件时或者放在内存时,又或者是在网络传输的过程当中。
但若是咱们要说,字符集编码这一律念也能够包含抽象层面的编码,那么这样一种说法也并没有不妥,只要你能准确区分这两个层面,你怎么去看待它们都是能够的,仍是前面那句话,这取决于你如何定义编码,好比咱们能够说GBK中的区位码难道不是字符集编码吗?这就取决于你如何看待GBK编码这一律念了,是狭义的去看待仍是广义的去看待。
我不想为字符集编码的准肯定义去争论,在我看来,当你说编码时,只要你本身清楚说的是哪一个层面就ok了,咱们在前面引入了编号,目的是为在还没有澄清以前做更好的区分,若是你如今已经清楚了,就能够把编号丢掉了。
事实上,当人们说到Unicode编码时,更常是指它的抽象编码层,即码点这一层面,实际上这才是Unicode的核心所在。
Unicode的核心就是为每一个字符提供惟一一个数字编号。Unicode provides a unique number for every character.
让咱们来看一个更加具体的示意图:
关于码点如何具体转换成各类编码,这个在后面再做讨论。从图上咱们能够初步得出一些结论。好比
UTF-8与UTF-16都是变长编码,UTF-32则是定长编码。
码点到UTF-32的转换最简单,就是在前面垫0垫够4字节就好了。
码点到UTF-8的转换,除了最小那个在数值上同样外,其它两个彻底看不出二者的关系。
码点到UTF-16的转换则是最微妙的,能够看出前两个字符UTF-16与码点是彻底一致的,但那个大码点(准确地说是超过了U+FFFF的码点)则有了很大的变化,长度变成了四字节,值也变得很不同了。
关于UTF-16的误解是不少的,部分可能因为它的名字上带了个16,让人误觉得它是16位定长的两字节编码。但正像UTF-8并非仅仅是8位同样,UTF-16也不只仅是16位。
事实上,UTF-16的前身UCS-2确实是16位定长的编码,它跟码点在形式上就是彻底同样了,实际我很怀疑那时候压根就没码点这一说法,那时人们甚至也不说UCS-2,直接就叫Unicode!
时至今天,你依然能够在很多地方看到把UTF-16写成Unicode的,而后与UTF-8并排在一块儿,显得不三不四的,固然了,这是有历史缘由的。
简单地说,字符扩充了,目前码点的范围是U+0000~U+10FFFF。U+10FFFF是多大呢?大概是111万。
按Unicode官方的说法,就这样了,之后也不扩充了,一百多万足够用了,目前也只是定义了10万多个字符左右。
而16位定长的话,撑死了也就6万多,因此不变就不行了。
在后面的篇章中,将进一步分析定长,变长的问题。