zoukankan      html  css  js  c++  java
  • 字符编码

      在计算机中,所有的数据在存储和运算时都要使用二进制数表示(因为计算机用高电平和低电平分别表示1和0),而8个二进制位(bit)组合称为一个字节(Byte),所以一个字节能够组合出256中状态,即从00000000到11111111。

    1.标准ASCII

      ASCII码使用指定的7位或8位二进制数组合来表示128或256种可能的字符。标准ASCII码也叫基础ASCII码,使用7位二进制数来表示所有的大写和小写字母,数字0到9、标点符号,以及在美式英语中使用的特殊控制字符。其中:

    0-31及127(共33个)是控制字符或通信专用字符(其余为可显示字符),如控制符:LF(换行)、CR(回车)、FF(换页)、DEL(删除)、BS(退格)、BEL(响铃)等;通信专用字符:SOH(文头)、EOT(文尾)、ACK(确认)等;ASCII值为8、9、10和13分别转换为退格、制表、换行和回车字符。它们并没有特定的图形显示,但会依不同的应用程序,而对文本显示有不同的影响。

    32-126(共95个)是字符(32是空格),

    48-57为0到9十个阿拉伯数字。

    65-90为26个大写英文字母,

    97-122号为26个小写英文字母,

    其余为一些标点符号、运算符号等。

      在标准ASCII中,其最高位(b7)用作奇偶校验位。所谓奇偶校验,是指在代码传送过程中用来检验是否出现错误的一种方法,一般分奇校验和偶校验两种。奇校验规定:正确的代码一个字节中1的个数必须是奇数,若非奇数,则在最高位b7添1;偶校验规定:正确的代码一个字节中1的个数必须是偶数,若非偶数,则在最高位b7添1。

    2.扩展ASCII  

      一个字节中的后7位总共只能表示128个不同的字符,英语用这些字符已经足够了,可是要表示其他语言却是不够。比如,在法语中,字母上方有注音的符号,就无法用ASCII表示。于是,一些国家就利用了字节中闲置的最高位编入新的符号。这样一来,就可以表示最多256个符号,这就是扩展的ASCII 。

    3.GB2312, GBK

      后来,中国也引进了计算机,发现常用的汉字有6000多个,但是在ASCII编码方案中的所有字符已经被使用殆尽。此时,GB2312编码应运而生。GB2312编码方案规定:两个范围在0x80~0xFF 的字符表示一个汉字。0x00~0x7F之间的字符,依旧是1个字节代表1个字符。,但两个大于127的字符连在一起时,就表示一个汉字。这样就可以组合出大约7000多个简体汉字。注意上面提到的包含欧洲符号的扩展ASCII码在GB2312中也就不复存在了。

      后来,使用GB2312编码方案还是无法表示某些字,于是干脆不再要求低字节一定是0x80~0xFF范围内的字符,只要第一个字节是大于127就固定表示这是一个汉字的开始,不管后面跟的是不是扩展字符集里的内容。这既是GBK编码,GBK 包括了 GB2312 的所有内容,同时又增加了近20000个新的汉字(包括繁体字)和符号。

         在解码的时候,一次读取一个字节的内容,看一下该字节的最高位是否为1(说明范围在0x80~0xFF),如果为1,暂存该字节,并读取下一个字节,这样将两个字节合并然后去查询对应的字符;如果第一次读到的一个字节最高位为0,那么就按此字节的内容直接查询传统的ASCII码表,找到对应的字符。

    4.ANSI

       为在windows系统中ANSI并不是某一种特定的字符编码,而是在不同语言的系统中,ANSI表示不同的编码。如在简体中文Windows操作系统中,ANSI编码代表GBK编码;在日文Windows操作系统中,ANSI编码代表Shift_JIS编码。所以你用ANSI格式的txt文档存储的中文在其他语言系统中会出现乱码。微软用一个叫“Windows code pages”(在命令行下执行chcp命令可以查看当前code page的值)的值来判断系统默认编码,比如:简体中文的code page值为936(它表示GBK编码,win95之前表示GB2312,详见:Microsoft Windows' Code Page 936),繁体中文的code page值为950(表示Big-5编码)。

      这里还需要提一下ANSI背景下的全角和半角的概念:全角是指中由两个字符表示的各种符号。半角是指英文ASCII码中的各种符号。

    5.Unicode

      由于各个国家的不同编码标准,导致相互之间谁也理解不了对方的编码,所以当时是必须安装对应的字符系统才能解读存储的内容。之后ISO (国际标准化组织)的国际组织决定着手解决这个问题,制定了Unicode字符集,涵盖了目前人类使用的所有字符,并为每个字符进行统一编号,分配唯一的字符码。

      需要注意的是,广义上的Unicode只是一个符号集,它只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储。比如,汉字 “严” 的unicode是十六进制数0x4E25,转换成二进制数足足有15位(1001110 00100101),也就是说这个符号的表示至少需要2个字节。表示其他更大的符号,可能需要3个字节或者4个字节,甚至更多。

      Unicode的最初目标,是用1个16位的编码来为超过65000字符提供映射。但这还不够,它不能覆盖全部历史上的文字,也不能解决传输的问题(implantation head-ache's),尤其在那些基于网络的应用中。已有的软件必须做大量的工作来处理16位的数据。 因此,Unicode用一些基本的保留字符制定了三套编码方式。它们分别是UTF-8, UTF-16和 UTF-32。正如名字所示,在UTF-8中,字符是以8位序列来编码的,用一个或几个字节来表示一个字符。这种方式的最大好处,是UTF-8保留了ASCII字符的编码做为它的一部分,例如,在UTF-8和ASCII中,“A”的编码都是0x41. UTF-16和UTF-32分别是Unicode的16位和32位编码方式。考虑到最初的目的,通常说的Unicode就是指UTF-16。

      注意Unicode背景下无论是半角的英文字母,还是全角的汉字,它们都是统一的“一个字符”,也就是统一的“两个字节"。

      但是Unicode还是存在一些问题:

      如何才能区别Unicode和ASCII?计算机怎么知道两个字节表示一个符号,而不是分别表示两个符号呢?

      英文字母只用一个字节表示就够了,如果Unicode统一规定,每个符号用两个字节表示,那么每个英文字母前都必然有一字节是0x00,这对于存储来说是极大的浪费,纯英文的文本文件的大小会因此大出一倍,这是无法接受的。

      UNICODE与GBK等两字节编码完全不兼容,无法找到一种简单的方式转换(只能使用查找表的方式)。

    6.Unicode 和 UCS

      根据维基百科全书的记载,历史上存在两个试图独立设计Unicode的组织,即国际标准化组织(ISO)和一个软件制造商的协会(unicode.org)。ISO开发了ISO 10646项目,Unicode协会开发了Unicode项目。

      在1991年前后,双方都认识到世界不需要两个不兼容的字符集。于是它们开始合并双方的工作成果,并为创立一个单一编码表而协同工作。从Unicode2.0开始,Unicode项目采用了与ISO 10646-1相同的字库和字码。

      Unicode在编码上和UCS保持一致,在实现上有自己的规则,而UCS只定义了编码标准。Unicode的实现形式上 有UTF-8,UTF-16,UTF-32,还有UTF-7等。UCS编码也有自己的格式:UCS-2和UCS-4等等。

      Unicode的编码可以和UCS-2和UCS-4保持一致。但是又略有不同。UTF-16是UCS-2的扩展,UTF-32是UCS-4的子集。也就是说,UTF-16的实现上对code point的支持范围超过UCS-2,而UTF-32对code point的表示却又在UCS-4的范围之内(下面第7点也会说到这个方面)。

      UCS只是规定如何编码,并没有规定如何传输、保存这个编码。例如“汉”字的UCS编码是 0x6C49,我可以用4个ASCII数字来传输、保存这个编码;也可以用utf-8编码:3个连续的字节0xE6 0xB1 0x89来表示它。关键在于通信双方都要认可。UTF-8、UTF-7、UTF-16都是被广泛接受的方案。UTF-8的一个特别的好处是它与ISO-8859-1完全兼容。UTF是 “UCS   Transformation Format”的缩写。

    7.UCS-2 UCS-4 和 BMP

      Unicode是为整合全世界的所有语言文字而诞生的。任何文字在Unicode中都对应一个值, 这个值称为代码点(code point)。代码点的值通常写成 U+ABCD 的格式。 而文字和代码点之间的对应关系就是UCS-2(Universal Character Set coded in 2 octets)。

      顾名思义,UCS-2是用两个字节来表示代码点,其取值范围为 U+0000~U+FFFF。为了能表示更多的文字,人们又提出了UCS-4,即用四个字节(实际上只用了31位,最高位必须为0)表示代码点。 它的范围为 U+00000000~U+7FFFFFFF,其中 U+00000000~U+0000FFFF和UCS-2是一样的。

      要注意,UCS-2和UCS-4只规定了代码点和文字之间的对应关系,并没有规定代码点在计算机中如何存储。 规定存储方式的称为UTF(Unicode Transformation Format),其中应用较多的就是UTF-16和UTF-8了。

      UCS-4根据最高位为0的最高字节分成2^7=128个group。每个group再根据次高字节分为256个plane。每个plane根据第3个字节分为256行 (rows),每行包含256个cells。当然同一行的cells只是最后一个字节不同,其余都相同。group 0的plane 0被称作Basic Multilingual Plane, 即BMP。或者说UCS-4中,高两个字节为0的码位被称作BMP。

      将UCS-4的BMP去掉前面的两个零字节就得到了UCS-2。在UCS-2的两个字节前加上两个零字节,就得到了UCS-4的BMP。而目前的UCS-4规范中还没有任何字符被分配在BMP之外。

      Unicode最初支持16位的code point,后来发现不够用,于是用UTF-16扩展UCS-2。在BMP区域内的一片连续空间(U+D800~U+DFFF)的码位区段是永久保留不映射到字符(至于为什么请看下面第9条),因此UTF-16利用保留下来的0xD800-0xDFFF区段的码位来对辅助平面的字符的码位进行编码。具体算法参考wiki:utf-16 .
    所以,utf-16能表示的范围最大能到U+10FFFF,包含1个基本平面(BMP)和16个辅助平面。

      理论上UCS-4编码范围能达到U+7FFFFFFF,但是因为Unicode和ISO达成共识,只会用17个平面内的字符,所以UTF-32是UCS-4的子集。但是UTF-16是定长的编码,和UCS-4无论实现和编码都是基本一样的。

         当前,Unicode深入人心,且UTF-8大行其道,UCS编码基本被等同于UTF-16,UTF-32了,所以目前UCS基本谈出人们的视野中。(Windows NT用的就是UCS-2)

    8.Unicode 的 Big Endian 和 Little Endian

      Endian是CPU处理多字节数的不同方式。例如 “汉” 字的Unicode编码是 0x6C49。那么写到文件里时,究竟是将 0x6C 写在前面,还是将 0x49 写在前面?

      如果将 0x6C 写在前面,就是Big Endian。如果将 0x49 写在前面,就是Little Endian。

    9.UTF-16

      UTF-16由RFC2781规定,它使 用两个字节来表示一个代码点。

      UTF-16是完全对应于UCS-2的,即把UCS-2规定的代码点通过Big Endian或Little Endian方式 直接保存下来。UTF-16包括三种:UTF-16,UTF-16BE(Big Endian),UTF-16LE(Little Endian)。

      UTF-16BE和UTF-16LE不难理解,而UTF-16就需要通过在文件开头以名为BOM(Byte Order Mark)的字符 来表明文件是Big Endian还是Little Endian。BOM为U+FEFF这个字符。

      其实BOM是个小聪明的想法。由于UCS-2没有定义U+FFFE, 因此只要出现 FF FE 或者 FE FF 这样的字节序列,就可以认为它是U+FEFF, 并且可以判断出是Big Endian还是Little Endian。

      举个例子。“ABC”这三个字符用各种方式编码后的结果如下:

    UTF-16BE 00 41 00 42 00 43
    UTF-16LE 41 00 42 00 43 00
    UTF-16(Big Endian) FE FF 00 41 00 42 00 43
    UTF-16(Little Endian) FF FE 41 00 42 00 43 00
    UTF-16(不带BOM) 00 41 00 42 00 43

      Windows平台下默认的Unicode编码为Little Endian的UTF-16(即上述的 FF FE 41 00 42 00 43 00)。

      另外,UTF-16还能表示一部分的UCS-4代码点——U+10000~U+10FFFF。 表示算法比较复杂,简单说明如下:

      1.从代码 点U中减去0x10000,得到U'。这样U+10000~U+10FFFF就变成了 0x00000~0xFFFFF。

      2.用20位二进制数表示U'。 U'=yyyyyyyyyyxxxxxxxxxx

      3.将前10位和后10位用W1和W2表示,W1=110110yyyyyyyyyy,W2=110111xxxxxxxxxx,则 W1 = D800~DBFF,W2 = DC00~DFFF。

      例如,U+12345表示为 D8 08 DF 45(UTF-16BE),或者08 D8 45 DF(UTF-16LE)。

      但是由于这种算法的存在,造成UCS-2中的 U+D800~U+DFFF 变成了无定义的字符。

    10.UTF-32

      UTF-32用四个字节表示代码点,这样就可以完全表示UCS-4的所有代码点,而无需像UTF-16那样使用复杂的算法。 与UTF-16类似,UTF-32也包括UTF-32、UTF-32BE、UTF-32LE三种编码,UTF-32也同样需要BOM字符。 仅用'ABC'举例:

    UTF-32BE 00 00 00 41 00 00 00 42 00 00 00 43
    UTF-32LE 41 00 00 00 42 00 00 00 43 00 00 00
    UTF-32(Big Endian) 00 00 FE FF 00 00 00 41 00 00 00 42 00 00 00 43
    UTF-32(Little Endian) FF FE 00 00 41 00 00 00 42 00 00 00 43 00 00 00
    UTF-32(不带BOM) 00 00 00 41 00 00 00 42 00 00 00 43

    11.UTF-8

      UTF-16和UTF-32的一个缺点就是它们固定使用两个或四个字节, 这样在表示纯ASCII文件时会有很多00字节,造成浪费。 而RFC3629定义的UTF-8则解决了这个问题

      UTF-8最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号(理论上最多可以用6个字节表示一个符号),根据不同的符号而变化字节长度。

      UTF-8的编码规则很简单,只有两条:

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

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

      下表总结了编码规则,字母x表示可用编码的位。

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

      以汉字“严”为例,演示如何实现UTF-8编码。

      已知“严”的unicode是 0x4E25 (01001110 00100101),根据上表,可以发现 0x4E25 处在第三行的范围内(0000 0800-0000 FFFF),因此“严”的UTF-8编码需要三个字节,即格式是“1110xxxx 10xxxxxx 10xxxxxx”。然后,从“严”的最后一个二进制位开始,依次从后向前填入格式中的x,多出的位补0。这样就得到了,“严”的UTF-8编码是“11100100 10111000 10100101”,转换成十六进制就是 0xE4B8A5。

    12.BOM

      BOM(byte-order mark),即字节顺序标记,它是插入到以UTF-8、UTF16或UTF-32编码Unicode文件开头的特殊标记,用来识别Unicode文件的编 码类型。对于UTF-8来说,BOM并不是必须的,因为BOM用来标记多字节编码文件的编码类型和字节顺序(big-endian或little- endian)。

      为了识别 Unicode 文件,Microsoft 建议所有的 Unicode 文件应该以 ZERO WIDTH NOBREAK SPACE(U+FEFF)字符开头。这作为一个“特征符”或“字节顺序标记(byte-order mark,BOM)”来识别文件中使用的编码和字节顺序。

      Linux/UNIX 并没有使用 BOM,因为它会破坏现有的 ASCII 文件的语法约定。

      不同的编辑工具对BOM的处理也各不相同。使用Windows自带的记事本将文件保存为UTF-8编码的时候,记事本会自动在文件开头插入BOM(虽然BOM对UTF-8来说并不是必须的),但是editplus就不会这样做。

      以下是摘自维基百科中的BOM表:

    EncodingRepresentation (hexadecimal)Representation (decimal)Bytes as CP1252 characters
    UTF-8[t 1] EF BB BF 239 187 191 
    UTF-16 (BE) FE FF 254 255 þÿ
    UTF-16 (LE) FF FE 255 254 ÿþ
    UTF-32 (BE) 00 00 FE FF 0 0 254 255 ␀␀þÿ (␀ refers to the ASCII null character)
    UTF-32 (LE) FF FE 00 00 255 254 0 0 ÿþ␀␀ (␀ refers to the ASCII null character)
    UTF-7[t 1] 2B 2F 76 38
    2B 2F 76 39
    2B 2F 76 2B
    2B 2F 76 2F[t 2]
    2B 2F 76 38 2D[t 3]
    43 47 118 56
    43 47 118 57
    43 47 118 43
    43 47 118 47
    43 47 118 56 45
    +/v8
    +/v9
    +/v+
    +/v/
    +/v8-
    UTF-1[t 1] F7 64 4C 247 100 76 ÷dL
    UTF-EBCDIC[t 1] DD 73 66 73 221 115 102 115 Ýsfs
    SCSU[t 1] 0E FE FF[t 4] 14 254 255 ␎þÿ (␎ represents the ASCII "shift out" character)
    BOCU-1[t 1] FB EE 28 251 238 40 ûî(
    GB-18030[t 1] 84 31 95 33 132 49 149 51 „1•3

      

      看了上面的内容你也许会问,没有BOM的情况下,系统会不会因为不知道文本的编码方式而导致乱码?

      确实有这么个问题存在,最常见的就是你在简体中文的系统下新建一个txt文件,里面只写入“联通”两个字,保存后再重新打开文本,会发现读出来的是乱码。

      因为当你新建一个文本文件时,记事本的编码默认是ANSI, 如果你在ANSI的编码输入汉字,那么他实际就是GB系列的编码方式,在这种编码下,"联通"的内码是:
      0xC1 1100 0001
      0xAA 1010 1010
      0xCD 1100 1101
      0xA8 1010 1000

      第一二个字节、第三四个字节的起始部分的都是"110"和"10",正好与UTF8规则里的两字节模板是一致的,于是再次打开记事本时,记事本就误认为这是一个UTF8编码的文件,我们把第一个字节的110和第二个字节的10去掉,就得到了"00001 101010",再把各位对齐,补上前导的0,就得到了"0000 0000 0110 1010",这是UNICODE的006A,也就是小写的字母"j",而之后的两字节用UTF8解码之后是0x0368,这个字符什么也不是。这就是"联通"两个字的文件没有办法在记事本里正常显示的原因。而如果你在"联通"之后多输入几个字,其他的字的编码不见得又恰好是110和10开始的字节,这样再次打开时,记事本就不会坚持这是一个utf8编码的文件,而会用ANSI的方式解读之,这时乱码又不出现了。

  • 相关阅读:
    Nginx +keepalived
    iptables
    编译安装HTTPD 2.4.9版本
    Apache+lvs高可用+keepalive(主从+双主模型)
    LVS-HA
    corosync+pacemaker实现高可用(HA)集群
    NTP
    私人定制自己的linux小系统
    大数据处理-bitmap是个神马东西
    CSS3制作苹果风格键盘
  • 原文地址:https://www.cnblogs.com/zoneofmine/p/6246872.html
Copyright © 2011-2022 走看看