zoukankan      html  css  js  c++  java
  • 字符编码,我所不知道的

    转载:http://www.cnblogs.com/KevinYang/archive/2010/06/18/1760597.html


    字符编码的问题看似很小,经常被技术人员忽视,但是很容易导致一些莫名其妙的问题。这里总结了一下字符编码的一些普及性的知识,希望对大家有所帮助。

    还是得从ASCII 码说起

     

    说到字符编码,不得不说ASCII 码的简史。计算机一开始发明的时候是用来解决数字计算的问题,后来人们发现,计算机还可以做更多的事,例如文本处 理。但由于计算机只识 ,因此人们必须告诉计算机哪个数字来代表哪个特定字符,例如65 代表字母‘A’66 代表字母‘B’ ,以此类推。但是计算机之间字符- 数字的对应关系必须得一致,否则就会造成同一段数字在不同计算机上显示出来的字符不一样 。因此美国国家标准协会ANSI 制定了一个标准,规定了常用字符的集合以及每个字符对应的编号,这就是ASCII 字符集(Character Set ),也称ASCII 码。

    当时的计算机普遍使用8 比特字节作为最小的存储和处理单元,加之当时用到的字符也很少,26 个大小写英文字母还有数字再加上其他常用符号,也不到100 个,因此使用7 个比特位就可以高效的存储和处理ASCII 码,剩下最高位1 比特被用作一些通讯系统的奇偶校验。

    注意,字节代表系统能够处理的最小单位,不一定是8 比特。只是现代计算机的事实标准就是用8 比特来代表一个字节。在很多技术规格文献中,为了避免产 生歧义,更倾向于使用8 位组(Octet )而不是字节(Byte )这个术语来强调8 个比特的二进制流。下文中为了便于理解,我会延用大家熟悉的 字节 这 个概念。

    说明: ASCII table

    ASCII 字符集由95 个可打印字符(0x20-0x7E )和33 个控制字符(0x00-0x190x7F )组成。可打印字符用于显示在输出设备 上,例如荧屏或者打印纸上,控制字符用于向计算机发出一些特殊指令,例如0x07 会让计算机发出哔的一声,0x00 通常用于指示字符串的结束,0x0D 0x0A 用于指示打印机的打印针头退到行首(回车)并移到下一行(换行)。

    那时候的字符编解码系统非常简单,就是简单的查表过程。例如将字符序列编码为二进制流写入存储设备,只需要在ASCII 字符集中依次找到字符对应的字节,然后直接将该字节写入存储设备即可。解码二进制流的过程也是类似。

    OEM 字符集的衍生

    当计算机开始发展起来的时候,人们逐渐发现,ASCII 字符集里那可怜的128 个字符已经不能再满足他们的需求了。人们就在想,一个字节能够表示的 数字(编号)有256 个,而ASCII 字符只用到了0x00~0x7F ,也就是占用了前128 个,后面128 个数字不用白不用,因此很多人打起了后面这 128 个数字的主意。可是问题在于,很多人同时有这样的想法,但是大家对于0x80-0xFF 这后面的128 个数字分别对应什么样的字符,却有各自的想 法。这就导致了当时销往世界各地的机器上出现了大量各式各样的OEM 字符集。

    下面这张表是IBM-PC 机推出的其中一个OEM 字符集,字符集的前128 个字符和ASCII 字符集的基本一致(为什么说基本一致呢,是因为前32 个控制字符在某些情况下会被IBM-PC 机当作可打印字符解释),后面128 个字符空间加入了一些欧洲国家用到的重音字符,以及一些用于画线条画的字符。

    说明: IBM-PC OEM字符集

    事实上,大部分OEM 字符集是兼容ASCII 字符集的,也就是说,大家对于0x00~0x7F 这个范围的解释基本是相同的,而对于后半部分0x80~0xFF 的解释却不一定相同。甚至有时候同样的字符在不同OEM 字符集中对应的字节也是不同的。

    不同的OEM 字符集导致人们无法跨机器交流各种文档。例如职员甲发了一封简历résumés 给职员乙,结果职员乙看到的却是r说明: ג sum说明: ג s ,因为é 字符在职员甲机器上的OEM 字符集中对应的字节是0x82 ,而在职员乙的机器上,由于使用的OEM 字符集不同,对0x82 字节解码后得到的字符却是说明: ג

    多字节字符集(MBCS )和中文字符集

    上面我们提到的字符集都是基于单字节编码,也就是说,一个字节翻译成一个字符。这对于拉丁语系国家来说可能没有什么问题,因为他们通过扩展第8 个比 特,就可以得到256 个字符了,足够用了。但是对于亚洲国家来说,256 个字符是远远不够用的。因此这些国家的人为了用上电脑,又要保持和ASCII 字符 集的兼容,就发明了多字节编码方式,相应的字符集就称为多字节字符集。例如中国使用的就是双字节字符集编码(DBCSDouble Byte Character Set )。

    对于单字节字符集来说,代码页中只需要有一张码表即可,上面记录着256 个数字代表的字符。程序只需要做简单的查表操作就可以完成编解码的过程。

    代码页是字符集编码的具体实现,你可以把他理解为一张 字符- 字节 映射表,通过查表实现 字符- 字节 的翻译。下面会有更详细的描述。

    而对于多字节字符集,代码页中通常会有很多码表。那么程序怎么知道该使用哪张码表去解码二进制流呢?答案是,根据第一个字节来选择不同的码表进行解析

    例如目前最常用的中文字符集GB2312 ,涵盖了所有简体字符以及一部分其他字符;GBKK 代表扩展的意思)则在GB2312 的基础上加入了对繁 体字符等其他非简体字符(GB18030 字符集不是双字节字符集,我们在讲Unicode 的时候会提到)。这两个字符集的字符都是使用1-2 个字节来表 示。Windows 系统采用936 代码页来实现对GBK 字符集的编解码。在解析字节流的时候,如果遇到字节的最高位是0 的话,那么就使用936 代码页中的 第1 张码表进行解码,这就和单字节字符集的编解码方式一致了。

    说明: image

    当字节的高位是1 的时候,确切的说,当第一个字节位于0x81–0xFE 之间时,根据第一个字节不同找到代码页中的相应的码表,例如当第一个字节是0x81 ,那么对应936 中的下面这张码表:

    说明: image

    (关于936 代码页中完整的码表信息,参见MSDNhttp://msdn.microsoft.com/en-us/library/cc194913%28v=MSDN.10%29.aspx .

    按照936 代码页的码表,当程序遇到连续字节流0x81 0x40 的时候,就会解码为 字符。

    ANSI 标准、国家标准、ISO 标准

    不同ASCII 衍生字符集的出现,让文档交流变得非常困难,因此各种组织都陆续进行了标准化流程。例如美国ANSI 组织制定了ANSI 标准字符编码(注意,我们现在通常说到ANSI 编码,通常指的是平台的默认编码,例如英文操作系统中是ISO-8859-1 ,中文系统是GBK ),ISO 组织制定的各种ISO 标准字符编码,还有各国也会制定一些国家标准字符集,例如中国的GBKGB2312GB18030

    操作系统在发布的时候,通常会往机器里预装这些标准的字符集还有平台专用的字符集,这样只要你的文档是使用标准字符集编写的,通用性就比较高了。例 如你用GB2312 字符集编写的文档,在中国大陆内的任何机器上都能正确显示。同时,我们也可以在一台机器上阅读多个国家不同语言的文档了,前提是本机必 须安装该文档使用的字符集。

    Unicode 的出现

    虽然通过使用不同字符集,我们可以在一台机器上查阅不同语言的文档,但是我们仍然无法解决一个问题:在一份文档中显示所有字符 。为了解决这个问题,我们需要一个全人类达成共识的巨大的字符集,这就是Unicode 字符集。

    Unicode 字符集概述

    Unicode 字符集涵盖了目前人类使用的所有字符,并为每个字符进行统一编号,分配唯一的字符码(Code Point )。Unicode 字符集将所有字符按照使用上的频繁度划分为17 个层面(Plane ),每个层面上有216 =65536 个字符码空间。

    说明: image

    其中第0 个层面BMP ,基本涵盖了当今世界用到的所有字符。其他的层面要么是用来表示一些远古时期的文字,要么是留作扩展。我们平常用到的Unicode 字符,一般都是位于BMP 层面上的。目前Unicode 字符集中尚有大量字符空间未使用。

    编码系统的变化

    Unicode 出现之前,所有的字符集都是和具体编码方案绑定在一起的,都是直接将字符和最终字节流绑定死了,例如ASCII 编码系统规定使用7 比特来编码ASCII 字符集;GB2312 以及GBK 字符集,限定了使用最多2 个字节来编码所有字符,并且规定了字节序。这样的编码系统通常用简单的查 表,也就是通过代码页就可以直接将字符映射为存储设备上的字节流了。例如下面这个例子:

    说明: image

    这种方式的缺点在于,字符和字节流之间耦合得太紧密了,从而限定了字符集的扩展能力。假设以后火星人入住地球了,要往现有字符集中加入火星文就变得很难甚至不可能了,而且很容易破坏现有的编码规则。

    因此Unicode 在设计上考虑到了这一点,将字符集和字符编码方案分离开。

    说明: 字符编码系统

    也就是说,虽然每个字符在Unicode 字符集中都能找到唯一确定的编号(字符码,又称Unicode 码),但是决定最终字节流的却是具体的字符编码 。例如同样是对Unicode 字符“A” 进行编码,UTF-8 字符编码得到的字节流是0x41 ,而UTF-16 (大端模式)得到的是0x00 0x41

    常见的Unicode 编码

    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 编码,很多流行的编程平台,例如.NetJavaQt 还有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 个字节来编码字符,方法其实也非常简单。

    说明: image

    (上图中的x 代表Unicode 码的低8 位,y 代表高8 位)

    对于ASCII 字符的编码使用单字节,和ASCII 编码一摸一样, 这样所有原先使用ASCII 编解码的文档就可以直接转到UTF-8 编码了。对于其他字符,则使用2-4 个字节来表示,其中,首字节前置1 的数目代表正确解 析所需要的字节数,剩余字节的高2 位始终是10 。例如首字节是1110yyyy ,前置有31 ,说明正确解析总共需要3 个字节,需要和后面2 个以10 开头 的字节结合才能正确解析得到字符

    关于UTF-8 的更多信息,参考维基百科:UTF-8

    GB18030

    任何能够将Unicode 字符映射为字节流的编码都属于Unicode 编码。中国的GB18030 编码,覆盖了Unicode 所有的字符,因此也算 是一种Unicode 编码。只不过他的编码方式并不像UTF-8 或者UTF-16 一样,将Unicode 字符的编号通过一定的规则进行转换,而只能通过查 表的手段进行编码。

    关于GB18030 的更多信息,参考:GB18030

    Unicode 相关的常见问题

    Unicode 是两个字节吗?

    Unicode 只是定义了一个庞大的、全球通用的字符集,并为每个字符规定了唯一确定的编号,具体存储为什么样的字节流,取决于字符编码方案。推荐的Unicode 编码是UTF-16UTF-8

    带签名的UTF-8 指的是什么意思?

    带签名指的是字节流以BOM 标记开始。很多软件会 智能 的探测当前字节流使用的字符编码,这种探测过程出于效率考虑,通常会提取字节流前面若干个 字节,看看是否符合某些常见字符编码的编码规则。由于UTF-8ASCII 编码对于纯英文的编码是一样的,无法区分开来,因此通过在字节流最前面添加 BOM 标记可以告诉软件,当前使用的是Unicode 编码,判别成功率就十分准确了。但是需要注意,不是所有软件或者程序都能正确处理BOM 标记,例如 PHP 就不会检测BOM 标记,直接把它当普通字节流解析了。因此如果你的PHP 文件是采用带BOM 标记的UTF-8 进行编码的,那么有可能会出现问题。

    Unicode 编码和以前的字符集编码有什么区别?

    早期字符编码、字符集和代码页等概念都是表达同一个意思。例如GB2312 字符集、GB2312 编码,936 代码页,实际上说的是同个东西。但是对 于Unicode 则不同,Unicode 字符集只是定义了字符的集合和唯一编号,Unicode 编码,则是对UTF-8UCS-2/UTF-16 等具体 编码方案的统称而已,并不是具体的编码方案。所以当需要用到字符编码的时候,你可以写gb2312codepage936utf-8utf-16 , 但请不要写unicode (看过别人在网页的meta 标签里头写charset=unicode ,有感而发)。

     

  • 相关阅读:
    i++与++i的区别和使用
    C++中函数返回引用
    ASP.NET金课设计(四)
    ASP.NET金课设计(三)
    ASP.NET金课设计(二)
    ASP.NET金课--课程大纲
    使用PagerTemplate实现GridView分页
    后台模块--订单管理
    前台模块--首页
    后台模块--公告管理
  • 原文地址:https://www.cnblogs.com/zengqh/p/2477419.html
Copyright © 2011-2022 走看看