• 字符编码的前世今生


    【注:本文重点只对文本编码进行讲解,视频、图片、语音的编码和压缩待我今后理解透彻后再做深入讲述】

      人类先有了自己的语言,交流了若干个世纪,然后出现了计算机。我们知道,由于计算机只能识别0和1(冯·洛伊曼体系结构),因而所有的信息最终都需要表示为一个二进制的字符串,字符串的每一个二进制位(bit)有0和1两种状态。比如,当我们需要把字符'A'存入计算机时,应该对应哪种状态呢,存储时,我们可以将字符'A'用 01000001 (姑且这么认为)二进制字符串表示,存入计算机;读取时,再将 01000001 还原成字符'A'。

    因而可以总结为:

     

    1.将字符转换为字节(8位的二进制字符串)的方式称为编码;        ——传输和存储的时候

     

     2.将字节转换为字符的方式称为解码。            ——读出并显示的时候

      那么问题来了,当我们每一个人与计算机进行交互的时候,存储时,字符'A'应该对应哪一串二进制数呢,是01000010(一个字节),还是11110101 呢?说白了,就是需要一个统一的规则。这个规则可以将字符映射到唯一一种状态(二进制字符串),这就是编码我们需要一张表把我们人类的语言一一对应成计算机能够识别的语言,这张表就是我们通常所说的字符编码表

      因为计算机是美国人发明的,在设计之初的时候并未考虑到全世界所使用的语言的情况,所以最开始只有一张ASCII表,这个表只是英文和计算机识别语言的一一对应,一共规定了128个字符的编码,比如空格"SPACE"是32(十进制)(二进制00100000),大写的字母A是65(二进制01000001)。这128个符号(包括 32个不能打印出来的控制符号),只占用了一个字节(8 bit)的后面7位,最前面的1位统一规定为0

      ASCII表总共才有128个字符编码,一个字节都没有用完,这好像似乎有点太少了。于是乎,就开始压榨最高位,对其为1时也进行编码,利用最高位进行编码的方式就称为非ASCII编码,如 ISO-8859-1 编码。

      ISO-8859-1编码规则由ISO组织制定。是在 ASCII 码基础上又制定了一些标准用来扩展ASCII编码,即 00000000(0) ~ 01111111(127) 与ASCII的编码一样,对 10000000(128) ~ 11111111(255)这一段进行了编码,如将字符§编码成 10100111(167)。ISO-8859-1编码也是单字节编码,最多能够表示256个字符Latin1是ISO-8859-1的别名,有些环境下写作Latin-1。由于ISO-8859-1编码是单字节编码,和计算机最基础的表示单位一致,所以很多时候,仍旧使用 ISO8859-1编码来表示。而且在很多协议上,默认使用该编码。

      即使ISO-8859-1编码能够表示256个字符,但是对中文而言,还是太少了。一个字节肯定不够,必须用多个字节表示。比如,虽然"中文"两个字不存在ISO8859-1编码,以GB2312编码为例,应该是D6D0 CEC4两个字符,使用ISO8859-1编码的时候则将它拆开为4个字节来表示:D6 D0 CE C4(事实上,在进行存储的时候,也是以字节为单位进行处理)。而如果是UTF编码,则是6个字节e4 b8 ad e6 96 87。很明显,这种表示方法还需要以另一种编码为基础才能正确显示。而常见的中文编码方式有GB2312、BIG5、GBK。

      GB2312是第一个汉字编码国家标准,由中国国家标准总局1980年发布,1981年5月1日开始使用。GB2312编码共收录汉字6763个,其中一级汉字3755个,二级汉字3008个。同时,GB2312编码收录了包括拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母在内的682个全角字符。GB2312编码对所收录字符进行了"分区"处理,共94个区,区从1(十进制)开始,一直到94(十进制),每区含有94个位,位从1(十进制)开始,一直到94(十进制),共8836(94 * 94)个码位,这种表示方式也称为区位码,GB2312是双字节编码,其中第一个字节为“高字节”,对应94个区;第二个字节为“低字节”,对应94个位。所以它的区位码范围(用的十进制表示)是:0101~9494区号和位号分别加上十六进制数0xA0就是GB2312编码。例如最后一个码位是9494,区号和位号转换成十六进制都是5E5E,再加上十六进制数0xA0(注:0x是十六进制数的标识符):0x5E+0xA0=0xFE,所以该码位的GB2312编码是FEFE。GB2312编码范围:A1A1~FEFE,其中汉字的编码范围为B0A1~F7FE,第一字节0xB0~0xF7(对应区号:16~87),第二个字节0xA1~0xFE(对应位号:01~94)。

    各区总体说明如下:

    【点击图片可链接查看各区位的汉字表】

       比如“啊”字是GB2312编码中的第一个汉字,它位于16区的01位,所以它的区位码就是1601:

          

      BIG5编码又称大五码,采用双字节编码,使用两个字节来表示一个字符。高位字节使用了0x81~0xFE,低位字节使用了0x40~0x7E,及0xA1~0xFE。该编码是繁体中文字符集编码标准,共收录13060个中文字,其中有二字为重复编码,即“兀、兀”(A461及C94A)和“嗀、嗀”(DCD1及DDFC)。具体的分区如下:

    8140-A0FE 保留给使用者自定义字符(造字区)
    A140-A3BF 标点符号、希腊字母及特殊符号。其中在A259-A261,收录了度量衡单位用字:兙兛兞兝兡兣嗧瓩糎。
    A3C0-A3FE 保留。此区没有开放作造字区用。
    A440-C67E 常用汉字,先按笔划再按部首排序。
    C6A1-F9DC 其它汉字。
    F9DD-F9FE 制表符。

        【点击此链接可以查看BIG5编码表

      GBK编码扩展了GB2312,完全兼容GB2312编码(如'李'字的GBK、GB2312编码均为C0EE),但其不兼容BIG5编码('長'字的BIG5编码为AAF8,GBK编码为E94C,'李'字的BIG5编码为A7F5 不等于C0EE),即如果使用GB2312编码,使用GBK解码是完全正常的,但是如果使用BIG5编码,使用GBK解码,会出现乱码。相比于GB2312编码,GBK编码了更多汉字,如'镕'字。GBK编码依然采用双字节编码方案,其编码范围:8140-FEFE,剔除xx7F码位,共23940个码位。能表示 21003 个汉字。点击这里,查看GBK编码。点击这里,可以查询中文的其他编码。

      在GBK之后又出现了GB18030编码,但是没有形成主流,故不做介绍,至此,中文编码的问题已经讲解完成。

      那么问题又来了,大陆网民与在海峡两岸网民交流时,若都使用GBK编码,则没有问题,若一方使用GBK编码,一方使用BIG5编码,那么就会出现乱码问题,这是在海峡两岸网民交流,如果漂洋过海进行交流呢,那就更容易出现乱码问题。这时候我们可能想,要是有一套全世界都通用的编码就好了。不要担心,这样的编码确实是存在的,那就是Unicode。【乱码在本文后面进行具体介绍】

      随着计算机的普及,各国为了使用计算机,陆陆续续的又出现了很多自己国家的字符编码表。但是这样就造成了另外一种现象:乱码。比如当中国使用外国的软件的时候,由于编码表不一样的问题导致无法解码出正确的原始字符,从而出现乱码。为了解决乱码的问题,有两个独立的, 创立单一字符集的尝试. 一个是国际标准化组织(ISO)的 ISO 10646 项目, 另一个是由多语言软件制造商组成的协会组织的 Unicode 项目.。它们把世界上所有的语言通过一张表一一映射,这样乱码的问题就解决了。在1991年前后, 两个项目的参与者都认识到, 世界不需要两个不同的单一字符集。 它们合并双方的工作成果, 并为创立一个单一编码表而协同工作.。两个项目仍都存在并独立地公布各自的标准, 但 Unicode 协会和 ISO/IEC JTC1/SC2 都同意保持 Unicode 和 ISO 10646 标准的码表兼容, 并紧密地共同调整任何未来的扩展。

      Unicode是指一张表,里面包含了可能出现的所有字符,每个字符对应一个数字,这个数字称为码点(Code Point),如字符'H'的码点为72(十进制),字符'李'的码点为26446(十进制)。Unicode表包含了1114112个码点,即从000000(十六进制)~10FFFF(十六进制)。地球上所有字符都可以在Unicode表中找到对应的唯一码点。【点击这里,查询字符对应的码点】。Unicode将码空间划分为17个平面,从00~10(十六进制,最高两位),即从0~16(十进制),每个平面有65536个码点(2^16),其中最重要的是第一个Unicode平面(码点从0000~FFFF),包含了最常用的字符,该平面被称为基本多语言平面(Basic Multilingual Plane),缩写为BMP,其他平面称为辅助平面(Supplementary Planes)在基本多语言平面內, 从D800到DFFF之间的码位区段是永久保留不映射到字符的, 因此UTF-16编码巧妙的利用了这保留下来的码位来对辅助平面内的字符进行编码,这点后面进行讲解。Unicode只是一个符号集,只规定的字符所对应的码点,并没有指定如何存储,如何进行存储出现了不同的编码方案,关于Unicode编码方案主要有两条主线:UCS和UTF。UCS主线由ISO/IEC进行维护管理,UTF主线由Unicode Consortium进行维护管理。

      UCS全称为"Universal Character Set",在UCS中主要有UCS-2和UCS-4

      UCS-2是定长字节的,固定使用2个字节进行编码,从0000(十六进制)~FFFF(十六进制)的码位范围,对应第一个Unicode平面。采用BOM(Byte Order Mark)机制该机制作用如下:1. 确定字节流采用的是大端序还是小端序。2. 确定字节流的Unicode编码方案。【大端序、小端序详见下文的UTF-16介绍】

      UCS-4是定长字节的,固定使用4个字节进行编码。也采用了BOM机制

      UTF全称为"Unicode Transformation Format",在UTF中主要有UTF-8,UTF-16和UTF-32

      UTF-8是一种变长编码方式,使用1-4个字节进行编码,可以节省空间从而达到减少IO操作时间的目的,利于节约网络流量UTF-8完全兼容ASCII,对于ASCII中的字符,UTF-8采用的编码值跟ASCII完全一致。UTF-8只是unicode的一种转换格式,和世界上其他的语言没有一一对应关系,是Unicode一种具体的编码实现。关于Unicode码点如何转化为UTF-8编码,可以参照如下规则:

      ① 对于单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码。因此对于英语字母,UTF-8编码和ASCII码是相同的。

      ② 对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的unicode码。

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

      举例说明:

      1、字符'A'的Unicode码点为65(十进制),根据上表,在第一行范围,则字符'A'的UTF-8编码为01000001。

      2、中文字符'李'的Unicode码点为26446(十进制),二进制为 01100111  01001110,十六进制为674E。根据上表,在第三行范围,则将'李'二进制代码从低位到高位依次填入x中,不足的填入0。得到UTF-8编码为11100110  10011101 10001110,即E69D8E(十六进制)。

      由上述编码规则可知,0000 0000 ~ 0000 FFFF(第一行到第三行)为Unicode第一个平面(基本多语言平面),而0001 0000 ~ 0010 FFFF(第四行)为Unicode其他平面(辅助平面)。在基本多语言平面对应了绝大多数常用的字符,需要使用1到3个字节来进行UTF-8编码;对于大于65535(十进制)的码点,即在辅助平面上的码点,需要使用4个字节来进行UTF-8编码。

      UTF-16则只使用2或4个字节编码。UTF-16也是Unicode一种具体的编码实现。关于Unicode如何转化为UTf-16编码规则如下

      ① 若Unicode码点在第一平面(BPM)中,则使用2个字节进行编码。 

      ② 若Unicode码点在其他平面(辅助平面),则使用4个字节进行编码。

      关于辅助平面的码点编码更详细解析如下:辅助平面码点被编码为一对16比特(四个字节)长的码元, 称之为代理对(surrogate pair), 第一部分称为高位代理(high surrogate)或前导代理(lead surrogates),码位范围为:D800-DBFF. 第二部分称为低位代理(low surrogate)或后尾代理(trail surrogates), 码位范围为:DC00-DFFF

      注意,高位代理的码位从D800到DBFF,而低位代理的码位从DC00到DFFF,总共恰好为D800-DFFF,这部分码点在第一平面内是保留的,不映射到任何字符,所以UTF-16编码巧妙的利用了这点来进行码点在辅助平面内的4字节编码。

      举例说明:

      1、字符'A'的Unicode码点为65(十进制),十六进制表示为41,在第一平面。根据规则,UTF-16采用2个字节进行编码。那么问题又来了,知道了采用两个字节编码,并且我们也知道计算机是以字节为单位进行存储,这两个字节应该表示为00 41(十六进制),还是41 00(十六进制)呢?这就引出了一个问题,需要用到之前提及的BOM机制来解决。

      表示为00 41意味着采用了大端序(Big endian),而表示为41 00意味着采用了小端序。那么计算机如何知道存储的字符信息采用了大端序还是小端虚呢?这就需要加入一些控制信息,具体是:采用大端序,则在文件前加入FE FF;采用小端序,则在文件前加入FF FE。这样,当计算开始读取时发现前两个字节为FE FF,就表示之后的信息采用的是小端序,反之,则是大端序。

     

      2、字符 (无法显示,只能截图显示),其Unicode码点为65902(十进制),十六进制为1016E,很显然,已经超出了第一平面(BMP)所能表示的范围。其在辅助平面内,根据规则,UTF-16采用4个字节进行编码。然而其编码不是简单扩展为4个字节(00 01 01 6E),而是采用如下规则进行计算。

      ① 使用Unicode码位减去10000(十六进制),得到的值扩展20位(因为Unicode最大为10 FF FF(十六进制),减去1 00 00(十六进制)后,得到的结果最大为0FFF FF(十六进制),即为20位,不足20位的,在高位加一个0,扩展至20位即可)。

      ② 将步骤一得到的20位,按照高十位和低十位进行分割。

      ③ 将步骤二的高十位扩展至2个字节,再加上D800(十六进制),得到高位代理或前导代理。取值范围是D800 - 0xDBFF。

      ④ 将步骤二的低十位扩展至2个字节,再加上DC00(十六进制),得到低位代理或后尾代理。取值范围是DC00 - 0xDFFF。

     根据这个规则,那么我们知道字符 的码点为1016E,减去10000得到016E,扩展至0016E(高位加 0),【二进制表示为 0000000000 0101101110 】,然后进行分割,分割后的高十位为00 0000 0000,十六进制为0000,加上D800为D800;分割后的低十位为01 0110 1110,十六进制为016E,加上DC00为DD6E;综合得到D8 00 DD 6E。即UTF-16编码为D8 00 DD 6E(也可为D8 0 DD 6E)。

      对于UTF-32是使用4个字节表示,也采用BOM机制,可以类比UTF-16,这里不再额外介绍。

    字符编码区别

    1 、UCS-2 与 UTF-16区别

      从上面的分析知道,UCS-2采用的两个字节进行编码。在0000到FFFF的码位范围内,它和UTF-16基本一致,为什么说基本一致,因为在UTF-16中从U+D800到U+DFFF的码位不对应于任何字符,而在使用UCS-2的时代,U+D800到U+DFFF内的值被占用。

      UCS-2只能表示BMP内的码点(只采用2个字节),而UTF-16可以表示辅助平面内的码点(采用4个字节)。

      我们可以抽象的认为UTF-16可看成是UCS-2的父集。在没有辅助平面字符(surrogate code points)前,UTF-16与UCS-2所指的意思基本一致。但当引入辅助平面字符后,想要表示辅助平面字符时,就只能用UTF-16编码了。

    2、 UCS -4与 UTF-16的区别

      在BMP上,UTF-16采用2个字节表示,而在辅助平面上,UTF-16采用的是4个字节表示。对于UCS-4,不管在哪个平面都采用的是四个字节表示。

    3、 为什么UTF-8编码不需要BOM机制

      因为在UTF-8编码中,其自身已经带了控制信息,如1110xxxx 10xxxxxx 10xxxxxx 10xxxxxx,其中1110就起到了控制作用,所以不需要额外的BOM机制

    关于乱码的问题

      乱码是显示在屏幕上才被认为是乱码,也就是说乱码取决于人的感官,乱码只有人才知道,计算机不认为这是乱码。

      出现乱码的原因就是文本字符编码过程与字节流解码过程使用了不同的编码格式,这个往往归咎于解码格式选择错误,也就是说在解码的过程中出现了问题。例如,我的字符是用utf-8编码,你用GBK解码那肯定出问题。因为文字按照utf-8的编码规则编成的0、1,按照GBK的规则解码回来的文字并不是原来 的文字,这时候就会出现乱码了。这种问题会出现在文件读写、网络编码传输、数据库存取上。只要牵涉到字符都有可能出现乱码,因为只要有字符就会有解码过 程

      还有一种 情况就是文件压根不是文本文件,也就是说根本就没有经过编码这个过程,那你去解码当然乱码了。比如64,你如果看做文本字符就是6和4两个字符,可以对应编码格式进行编码。如果看做是数字64,那对应的存储结构是01000000,就没有编码过程,也就不需要去解码。

      要搞清楚的一点就是同样的文本字符,经过不同的编码,在存储结构上是不一样的,但是代表的字符是一样的,不同编码真正的区别在于存储结构。反过来,相同的存储结构,经过不同的解码,对应的文本字符并不一样,但是在内存上结构上并没有改变。如果碰到乱码,不要慌张,因为原始存储结构一动没动,只不过用错了解码方式。就像一千个读者有一千个哈姆雷特一样,真实的哈姆雷特就在那里。

      目前现状来看,计算机内存中使用的编码方式是unicode。

      在我们进行编码和解码的过程中,如果出现了各国语言不一致的问题,我们需要通过unicode进行转换。

      

    编码类型

      不管是文本还是图片或视频,在计算机存储上都是一视同仁,全都是字节流。但是从方便人们阅读的角度上还是分为文本文件和二进制文件。文本文件的可视形式就是文本字符,在存储和显示时有文本字符编解码的过程,可以直接用文本编辑器阅 读。除文本文件以外就是二进制文件。不同类型的二进制文件都有相应的结构标准,例如java的class文件,前四个字节代表文件类型,后边两个字节代表 大版本号,再后边两个字节代表小版本号。具体哪些字节代表什么意思,值是float类型还是int类型,都有一定的标准,所以需要特定的软件按照标准去读 取解析。

      在不同的编程语言中,往往提供不同的类对文本文件和二进制文件进行读写。最常 用的就是文本文件的读写例如C#中有StreamReader和StreamWriter,Java中有BufferedReader和 BufferedWriter。还有二进制文件的读写例如C#中有BinaryReader和BinaryWriter,Java中有 DataInputStream和DataOutputStream。当然读写二进制文件的类也可以读写文本文件,因为文本文件和二进制文件的存储在本质 上是没有区别的,都是二进制。只不过专门读写文本文件的类封装的更好,读写文本文件更方便。

     

       

  • 相关阅读:
    文本效果
    C# 将数据导出到Execl汇总[转帖]
    using方法的使用
    存储过程的相关记录
    Dictionary 泛型字典集合[转帖]
    JS验证
    浅谈AutoResetEvent的用法 [转帖]
    聊聊xp和scrum在实战中的应用问题
    字体下载
    [转] 前端开发工程师如何在2013年里提升自己
  • 原文地址:https://www.cnblogs.com/zzp-biog/p/9752862.html
Copyright © 2020-2023  润新知