在生活中,纯数字没有意义,如110能够是分数,能够是电话号码,也能够是money,数字只有在具体语境中才有意义。计算机只能存储0和1,它的意义就靠编码方式来肯定,一样的数字在不一样的编码方式的解释下意义不一样。html
字符编码的问题看似很小,常常被技术人员忽视,可是很容易致使一些莫名其妙的问题。这里总结了一下字符编码的一些普及性的知识,但愿对你们有所帮助。编程
说到字符编码,不得不说ASCII码的简史。计算机一开始发明的时候是用来解决数字计算的问题,后来人们发现,计算机还能够作更多的事,例如文本处 理。但因为计算机只识“数”,所以人们必须告诉计算机哪一个数字来表明哪一个特定字符,例如65表明字母‘A’,66表明字母‘B’,以此类推。可是计算机之间字符-数字的对应关系必须得一致,不然就会形成同一段数字在不一样计算机上显示出来的字符不同。所以美国国家标准协会ANSI制定了一个标准,规定了经常使用字符的集合以及每一个字符对应的编号,这就是ASCII字符集(Character Set),也称ASCII码。windows
当时的计算机广泛使用8比特字节做为最小的存储和处理单元,加之当时用到的字符也不多,26个大小写英文字母还有数字再加上其余经常使用符号,也不到100个,所以使用7个比特位就能够高效的存储和处理ASCII码,剩下最高位1比特被用做一些通信系统的奇偶校验。服务器
注意,字节表明系统可以处理的最小单位,不必定是8比特。只是现代计算机的事实标准就是用8比特来表明一个字节。在不少技 术规格文献中,为了不产生歧义,更倾向于使用8位组(Octet)而不是字节(Byte)这个术语来强调8个比特的二进制流。下文中为了便于理解,我会 延用你们熟悉的“字节”这个概念。网络
ASCII字符集由95个可打印字符(0x20-0x7E)和33个控制字符(0x00-0x19,0x7F)组成。可打印字符用于显示在输出设备 上,例如荧屏或者打印纸上,控制字符用于向计算机发出一些特殊指令,例如0x07会让计算机发出哔的一声,0x00一般用于指示字符串的结束,0x0D和 0x0A用于指示打印机的打印针头退到行首(回车)并移到下一行(换行)。函数
那时候的字符编解码系统很是简单,就是简单的查表过程。例如将字符序列编码为二进制流写入存储设备,只须要在ASCII字符集中依次找到字符对应的字节,而后直接将该字节写入存储设备便可。解码二进制流的过程也是相似。网站
当计算机开始发展起来的时候,人们逐渐发现,ASCII字符集里那可怜的128个字符已经不能再知足他们的需求了。人们就在想,一个字节可以表示的 数字(编号)有256个,而ASCII字符只用到了0x00~0x7F,也就是占用了前128个,后面128个数字不用白不用,所以不少人打起了后面这 128个数字的主意。但是问题在于,不少人同时有这样的想法,可是你们对于0x80-0xFF这后面的128个数字分别对应什么样的字符,却有各自的想 法。这就致使了当时销往世界各地的机器上出现了大量各式各样的OEM字符集。编码
下面这张表是IBM-PC机推出的其中一个OEM字符集,字符集的前128个字符和ASCII字符集的基本一致(为何说基本一致呢,是由于前32 个控制字符在某些状况下会被IBM-PC机看成可打印字符解释),后面128个字符空间加入了一些欧洲国家用到的重音字符,以及一些用于画线条画的字符。spa
事实上,大部分OEM字符集是兼容ASCII字符集的,也就是说,你们对于0x00~0x7F这个范围的解释基本是相同的,而对于后半部分0x80~0xFF的解释却不必定相同。甚至有时候一样的字符在不一样OEM字符集中对应的字节也是不一样的。操作系统
不一样的OEM字符集致使人们没法跨机器交流各类文档。例如职员甲发了一封简历résumés给职员乙,结果职员乙看到的倒是rsum
s,由于é字符在职员甲机器上的OEM字符集中对应的字节是0x82,而在职员乙的机器上,因为使用的OEM字符集不一样,对0x82字节解码后获得的字符倒是
。
上面咱们提到的字符集都是基于单字节编码,也就是说,一个字节翻译成一个字符。这对于拉丁语系国家来讲可能没有什么问题,由于他们经过扩展第8个比 特,就能够获得256个字符了,足够用了。可是对于亚洲国家来讲,256个字符是远远不够用的。所以这些国家的人为了用上电脑,又要保持和ASCII字符 集的兼容,就发明了多字节编码方式,相应的字符集就称为多字节字符集。例如中国使用的就是双字节字符集编码(DBCS,Double Byte Character Set)。
对于单字节字符集来讲,代码页中只须要有一张码表便可,上面记录着256个数字表明的字符。程序只须要作简单的查表操做就能够完成编解码的过程。
代码页是字符集编码的具体实现,你能够把他理解为一张“字符-字节”映射表,经过查表实现“字符-字节”的翻译。下面会有更详细的描述。
而对于多字节字符集,代码页中一般会有不少码表。那么程序怎么知道该使用哪张码表去解码二进制流呢?答案是,根据第一个字节来选择不一样的码表进行解析。
例如目前最经常使用的中文字符集GB2312,涵盖了全部简体字符以及一部分其余字符;GBK(K表明扩展的意思)则在GB2312的基础上加入了对繁 体字符等其余非简体字符(GB18030字符集不是双字节字符集,咱们在讲Unicode的时候会提到)。这两个字符集的字符都是使用1-2个字节来表 示。Windows系统采用936代码页来实现对GBK字符集的编解码。在解析字节流的时候,若是遇到字节的最高位是0的话,那么就使用936代码页中的 第1张码表进行解码,这就和单字节字符集的编解码方式一致了。
当字节的高位是1的时候,确切的说,当第一个字节位于0x81
–0xFE之间时,根据第一个字节不一样找到代码页中的相应的码表,例如当第一个字节是0x81,那么对应936中的下面这张码表:
(关于936代码页中完整的码表信息,参见MSDN:http://msdn.microsoft.com/en-us/library/cc194913%28v=MSDN.10%29.aspx.)
按照936代码页的码表,当程序遇到连续字节流0x81 0x40的时候,就会解码为“丂”字符。
不一样ASCII衍生字符集的出现,让文档交流变得很是困难,所以各类组织都陆续进行了标准化流程。例如美国ANSI组织制定了ANSI标准字符编码(注意,咱们如今一般说到ANSI编码,一般指的是平台的默认编码,例如英文操做系统中是ISO-8859-1,中文系统是GBK),ISO组织制定的各类ISO标准字符编码,还有各国也会制定一些国家标准字符集,例如中国的GBK,GB2312和GB18030。
操做系统在发布的时候,一般会往机器里预装这些标准的字符集还有平台专用的字符集,这样只要你的文档是使用标准字符集编写的,通用性就比较高了。例 如你用GB2312字符集编写的文档,在中国大陆内的任何机器上都能正确显示。同时,咱们也能够在一台机器上阅读多个国家不一样语言的文档了,前提是本机必 须安装该文档使用的字符集。
虽然经过使用不一样字符集,咱们能够在一台机器上查阅不一样语言的文档,可是咱们仍然没法解决一个问题:在一份文档中显示全部字符。为了解决这个问题,咱们须要一个全人类达成共识的巨大的字符集,这就是Unicode字符集。
Unicode字符集涵盖了目前人类使用的全部字符,并为每一个字符进行统一编号,分配惟一的字符码(Code Point)。Unicode字符集将全部字符按照使用上的频繁度划分为17个层面(Plane),每一个层面上有216=65536个字符码空间。
其中第0个层面BMP,基本涵盖了当今世界用到的全部字符。其余的层面要么是用来表示一些远古时期的文字,要么是留做扩展。咱们日常用到的Unicode字符,通常都是位于BMP层面上的。目前Unicode字符集中尚有大量字符空间未使用。
在Unicode出现以前,全部的字符集都是和具体编码方案绑定在一块儿的,都是直接将字符和最终字节流绑定死了,例如ASCII编码系统规定使用7 比特来编码ASCII字符集;GB2312以及GBK字符集,限定了使用最多2个字节来编码全部字符,而且规定了字节序。这样的编码系统一般用简单的查 表,也就是经过代码页就能够直接将字符映射为存储设备上的字节流了。例以下面这个例子:
这种方式的缺点在于,字符和字节流之间耦合得太紧密了,从而限定了字符集的扩展能力。假设之后火星人入住地球了,要往现有字符集中加入火星文就变得很难甚至不可能了,并且很容易破坏现有的编码规则。
所以Unicode在设计上考虑到了这一点,将字符集和字符编码方案分离开。
也就是说,虽然每一个字符在Unicode字符集中都能找到惟一肯定的编号(字符码,又称Unicode码),可是决定最终字节流的倒是具体的字符编码。例如一样是对Unicode字符“A”进行编码,UTF-8字符编码获得的字节流是0x41,而UTF-16(大端模式)获得的是0x00 0x41。
UCS-2/UTF-16
若是要咱们来实现Unicode字符集中BMP字符的编码方案,咱们会怎么实现?因为BMP层面上有216=65536个字符码,所以咱们只须要两个字节就能够彻底表示这全部的字符了。
举个例子,“中”的Unicode字符码是0x4E2D(01001110 00101101),那么咱们能够编码为01001110 00101101(大端)或者00101101 01001110 (小端)。
UCS-2和UTF-16对于BMP层面的字符均是使用2个字节来表示,而且编码获得的结果彻底一致。不一样之处在于,UCS-2最 初设计的时候只考虑到BMP字符,所以使用固定2个字节长度,也就是说,他没法表示Unicode其余层面上的字符,而UTF-16为了解除这个限制,支 持Unicode全字符集的编解码,采用了变长编码,最少使用2个字节,若是要编码BMP之外的字符,则须要4个字节结对,这里就不讨论那么远,有兴趣能够参考维基百科:UTF-16/UCS-2。
Windows从NT时代开始就采用了UTF-16编码,不少流行的编程平台,例如.Net,Java,Qt还有Mac下的Cocoa等都是使用UTF-16做为基础的字符编码。例如代码中的字符串,在内存中相应的字节流就是用UTF-16编码过的。
UTF-8
UTF-8应该是目前应用最普遍的一种Unicode编码方案。因为UCS-2/UTF-16对于ASCII字符使用两个字节进行编码,存储和处理 效率相对低下,而且因为ASCII字符通过UTF-16编码后获得的两个字节,高字节始终是0x00,不少C语言的函数都将此字节视为字符串末尾从而致使 没法正确解析文本。所以一开始推出的时候遭到不少西方国家的抵触,大大影响了Unicode的推行。后来聪明的人们发明了UTF-8编码,解决了这个问 题。
UTF-8编码方案采用1-4个字节来编码字符,方法其实也很是简单。
(上图中的x表明Unicode码的低8位,y表明高8位)
对于ASCII字符的编码使用单字节,和ASCII编码一摸同样,这样全部原先使用ASCII编解码的文档就能够直接转到UTF- 8编码了。对于其余字符,则使用2-4个字节来表示,其中,首字节前置1的数目表明正确解析所须要的字节数,剩余字节的高2位始终是10。例如首字节是 1110yyyy,前置有3个1,说明正确解析总共须要3个字节,须要和后面2个以10开头的字节结合才能正确解析获得字符。
关于UTF-8的更多信息,参考维基百科:UTF-8。
GB18030
任何可以将Unicode字符映射为字节流的编码都属于Unicode编码。中国的GB18030编码,覆盖了Unicode全部的字符,所以也算 是一种Unicode编码。只不过他的编码方式并不像UTF-8或者UTF-16同样,将Unicode字符的编号经过必定的规则进行转换,而只能经过查 表的手段进行编码。
关于GB18030的更多信息,参考:GB18030。
Unicode是两个字节吗?
Unicode只是定义了一个庞大的、全球通用的字符集,并为每一个字符规定了惟一肯定的编号,具体存储为何样的字节流,取决于字符编码方案。推荐的Unicode编码是UTF-16和UTF-8。
带签名的UTF-8指的是什么意思?
带签名指的是字节流以BOM标记开始。不少软件会“智能”的探测当前字节流使用的字符编码,这种探测过程出于效率考虑,一般会提取字节流前面若干个 字节,看看是否符合某些常见字符编码的编码规则。因为UTF-8和ASCII编码对于纯英文的编码是同样的,没法区分开来,所以经过在字节流最前面添加 BOM标记能够告诉软件,当前使用的是Unicode编码,判别成功率就十分准确了。可是须要注意,不是全部软件或者程序都能正确处理BOM标记,例如 PHP就不会检测BOM标记,直接把它当普通字节流解析了。所以若是你的PHP文件是采用带BOM标记的UTF-8进行编码的,那么有可能会出现问题。
Unicode编码和之前的字符集编码有什么区别?
早期字符编码、字符集和代码页等概念都是表达同一个意思。例如GB2312字符集、GB2312编码,936代码页,实际上说的是同个东西。可是对 于Unicode则不一样,Unicode字符集只是定义了字符的集合和惟一编号,Unicode编码,则是对UTF-八、UCS-2/UTF-16等具体 编码方案的统称而已,并非具体的编码方案。因此当须要用到字符编码的时候,你能够写gb2312,codepage936,utf-8,utf-16, 但请不要写unicode(看过别人在网页的meta标签里头写charset=unicode,有感而发)。
乱码指的是程序显示出来的字符文本没法用任何语言去解读。通常状况下会包含大量或者?。乱码问题是全部计算机用户或多或少会遇到的问题。形成乱码的缘由就是由于使用了错误的字符编码去解码字节流,所以当咱们在思考任何跟文本显示有关的问题时,请时刻保持清醒:当前使用的字符编码是什么。只有这样,咱们才能正确分析和处理乱码问题。
例如最多见的网页乱码问题。若是你是网站技术人员,遇到这样的问题,须要检查如下缘由:
注意,网页解析的过程若是使用的字符编码不正确,还可能会致使脚本或者样式表出错。具体细节能够参考我之前写过的文章:文档字符集致使的脚本错误和Asp.Net页面的编码问题。
不久前看到某技术论坛有人反馈,WinForm程序使用Clipboard类的GetData方法去访问剪切板中的HTML内容时会出现乱码的问 题,我估计也是因为WinForm在获取HTML文本的时候没有用对正确的字符编码致使的。Windows剪贴板只支持UTF-8编码,也就是说你传入的 文本都会被UTF-8编解码。这样一来,只要两个程序都是调用Windows剪切板API编程的话,那么复制粘贴的过程当中不会出现乱码。除非一方在获取到 剪贴板数据以后使用了错误的字符编码进行解码,才会获得乱码(我作了简单的WinForm剪切板编程实验,发现GetData使用的是系统默认编码,而不 是UTF-8编码)。
关于乱码中出现?或者?,这里须要额外提一下,当程序使用特定字符编码解析字节流的时候,一旦遇到没法解析的字节流时,就会用或者?来替代。所以,一旦你最终解析获得的文本包含这样的字符,而你又没法获得原始字节流的时候,说明正确的信息已经完全丢失了,尝试任何字符编码都没法从这样的字符文本中还原出正确的信息来。
字符集(Character Set),字面上的理解就是字符的集合,例如ASCII字符集,定义了128个字符;GB2312定义了7445个字符。而计算机系统中提到的字符集准确来讲,指的是已编号的字符的有序集合(不必定是连续)。
字符码(Code Point)指的就是字符集中每一个字符的数字编号。例如ASCII字符集用0-127这连续 的128个数字分别表示128个字符;GBK字符集使用区位码的方式为每一个字符编号,首先定义一个94X94的矩阵,行称为“区”,列称为“位”,而后将 全部国标汉字放入矩阵当中,这样每一个汉字就能够用惟一的“区位”码来标识了。例如“中”字被放到54区第48位,所以字符码就是5448。而 Unicode中将字符集按照必定的类别划分到0~16这17个层面(Planes)中,每一个层面中拥有216=65536个字符码,所以Unicode总共拥有的字符码,也便是Unicode的字符空间总共有17*65536=1114112。
编码的过程是将字符转换成字节流。
解码的过程是将字节流解析为字符。
字符编码(Character Encoding)是将字符集中的字符码映射为字节流的一种具体实现方案。例如 ASCII字符编码规定使用单字节中低位的7个比特去编码全部的字符。例如‘A’的编号是65,用单字节表示就是0x41,所以写入存储设备的时候就是 b’01000001’。GBK编码则是将区位码(GBK的字符码)中的区码和位码的分别加上0xA0(160)的偏移(之因此要加上这样的偏移,主要是 为了和ASCII码兼容),例如刚刚提到的“中”字,区位码是5448,十六进制是0x3630,区码和位码分别加上0xA0的偏移以后就获得 0xD6D0,这就是“中”字的GBK编码结果。
代码页(Code Page)一种字符编码具体形式。早期字符相对少,所以一般会使用相似表格的形式将字符直接 映射为字节流,而后经过查表的方式来实现字符的编解码。现代操做系统沿用了这种方式。例如Windows使用936代码页、Mac系统使用EUC-CN代 码页实现GBK字符集的编码,名字虽然不同,但对于同一汉字的编码确定是同样的。
大小端的说法源自《格列佛游记》。咱们知道,鸡蛋一般一端大一端小,小人国的人们对于剥蛋壳时应从哪一端开始剥 起有着不同的见解。一样,计算机界对于传输多字节字(由多个字节来共同表示一个数据类型)时,是先传高位字节(大端)仍是先传低位字节(小端)也有着不 同样的见解,这就是计算机里头大小端模式的由来了。不管是写文件仍是网络传输,实际上都是往流设备进行写操做的过程,并且这个写操做是从流的低地址向高地 址开始写(这很符合人的习惯),对于多字节字来讲,若是先写入高位字节,则称做大端模式。反之则称做小端模式。也就是说,大端模式下,字节序和流设备的地 址顺序是相反的,而小端模式则是相同的。通常网络协议都采用大端模式进行传输,windows操做系统采用Utf-16小端模式。
参考连接: