黑马程序员技术交流社区

标题: 【济南校区】乱码原理分析(一)—常用编码底层详解 [打印本页]

作者: 大山哥哥    时间: 2019-2-27 23:18
标题: 【济南校区】乱码原理分析(一)—常用编码底层详解
本帖最后由 大山哥哥 于 2019-2-27 23:18 编辑

        我们在处理文件、浏览网页、编写程序时,时不时会碰到乱码的情况。乱码几乎总是令人心烦,让人困惑。本文先介绍几个常用的编码表,理解这些编码的原理,下一篇文章在继续介绍常用的几种编码,第三篇文章再理解为什么出现了乱码,如何解决,最终使我们对于乱码不再心烦和困惑。
        谈乱码,我们就要谈字符和文本的二进制表示。我们已经知道计算机都是以字节来存储的,每个字节8个bit位,所以我字符和文本的存储底层也是需要用二进制表示的。由于内容比较多,我们将分三篇文章来介绍。本节主要介绍各种编码,编码听起来比较复杂,文章也比较长,但其实并不复杂,请耐心阅读,让我们逐步来探讨。
ASCII

世界上虽然有各种各样的字符,但计算机发明之初没有考虑那么多,基本上只考虑了美国的需求,美国大概只需要128个字符,美国就规定了这128个字符的二进制表示方法,这个方法是一个标准,称为ASCII编码,全称是American Standard Code forInformation Interchange,美国信息互换标准代码。128个字符用7个位刚好可以表示,计算机存储的最小单位是byte,即8位,ASCII码中最高位设置为0,用剩下的7位表示字符。这7位可以看做数字0到127,ASCII码规定了从0到127个,每个数字代表什么含义。我们先来看数字32到126的含义,如下图所示,除了中文之外,我们平常用的字符基本都涵盖了,键盘上的字符大部分也都涵盖了。

数字32到126表示的这些字符都是可打印字符,0到31和127表示一些不可以打印的字符,这些字符一般用于控制目的,这些字符中大部分都是不常用的,下表列出了其中相对常用的字符。
Ascii码对美国是够用了,但对别的国家而言却是不够的,于是,各个国家的各种计算机厂商就发明了各种各样的编码方式以表示自己国家的字符,为了保持与Ascii码的兼容性,一般都是将最高位设置为1。也就是说,当最高位为0时,表示Ascii码,当为1时就是各个国家自己的字符。在这些扩展的编码中,在西欧国家中流行的是ISO 8859-1和Windows-1252,在中国是GB2312,GBK,GB18030和Big5,我们逐个来研究这些编码。
ISO 8859-1
        ISO 8859-1又称Latin-1,它也是使用一个字节表示一个字符,因为西欧的文字也都是字母拼接,只不过比不是26个英文字母罢了,其中0到127与Ascii一样,128到255规定了不同的含义。在128到255中,128到159表示一些控制字符,这些字符也不常用,就不介绍了。160到255表示一些西欧字符,如下图所示:
Windows-1252
        ISO 8859-1虽然号称是标准,用于西欧国家,但它连欧元()这个符号都没有,因为欧元比较晚,而标准比较早。实际使用中更为广泛的是Windows-1252编码,这个编码与ISO8859-1基本是一样的,区别只在于数字128到159,Windows-1252使用其中的一些数字表示可打印字符,这些数字表示的含义,如下图所示:
这个编码中加入了欧元符号以及一些其他常用的字符。基本上可以认为,ISO 8859-1已被Windows-1252取代,在很多应用程序中,即使文件声明它采用的是ISO8859-1编码,解析的时候依然被当作Windows-1252编码。
      HTML5甚至明确规定,如果文件声明的是ISO 8859-1编码,它应该被看作Windows-1252编码。为什么要这样呢?因为大部分人搞不清楚ISO 8859-1和Windows-1252的区别,当他说ISO 8859-1的时候,其实他实际指的是Windows-1252,所以标准干脆就这么强制了。
GB2312
      美国和西欧字符用一个字节就够了,但中文显然是不够的。中文第一个标准是GB2312。GB2312标准主要针对的是简体中文常见字符,包括约7000个汉字,不包括一些罕见词,不包括繁体字。GB2312固定使用两个字节表示汉字,在这两个字节中,最高位都是1,如果是0,就认为是Ascii字符。在这两个字节中,(下面出现的数字,只要是高于127的在计算机中都是负数哈,因为计算机存储的字节最高位表示正负,但是编码中常用最高位也作为数据位来讲,因为这样理解编码比较方便。)其中第一个字节范围是1010 0001(十进制161) - 1111 0111(十进制247),第一个字节范围是1010 0001(十进制161)  - 1111 1110(十进制254)。
比如,"贤哥"的GB2312编码是(十进制表示,由于我在计算机运算中,字节最高位是1则表示负数,所以Java的运行结果如下):
  
  
  -49, -51 -72, -25
GBK
        GBK建立在GB2312的基础上,向下兼容GB2312,也就是说,GB2312编码的字符和二进制表示,在GBK编码里是完全一样的。GBK增加了一万四千多个汉字,共计约21000汉字,其中包括繁体字。GBK同样使用固定的两个字节表示,其中第一个字节范围是1000 0001(十进制129)  -  1111 1110(十进制254),第二个字节范围是0100 0000(十进制64) - 0111 1110(十进制126)和1000 0000(十进制128) - 1111 1110(十进制254)。
需要注意的是,第二个字节是从64开始的(64属于byte正数范围,和ASCII的编码重合了),也就是说,第二个字节最高位可能为0。那怎么知道它是汉字的一部分,还是一个ASCII字符呢?
        其实很简单,因为汉字是用固定两个字节表示的,在解析二进制流的时候,如果第一个字节的最高位为1,那么就将下一个字节读进来一起解析为一个汉字,而不用考虑它的最高位,解析完后,跳到第三个字节继续解析。
GB18030
       GB18030向下兼容GBK,增加了五万五千多个字符,共七万六千多个字符。包括了很多少数民族字符,以及中日韩统一字符。用两个字节已经表示不了GB18030中的所有字符,GB18030使用变长编码,有的字符是两个字节,有的是四个字节。在两字节编码中,字节表示范围与GBK一样。在四字节编码中,第一个字节的值从1000 0001(十进制129) 到11111110(十进制254),第二个字节的值从0011 0000(十进制48)到0011 1001(十进制57),第三个字节的值从1000 0001(十进制129) 到11111110(十进制254),第四个字节的值从0011 0000(十进制48)到0011 1001(十进制57)。
       解析二进制时,如何知道是两个字节还是四个字节表示一个字符呢?很简单,看第二个字节的范围,如果是48到57就是四个字节表示,因为两个字节编码中第二字节都比这个大。所以这样综合说明GB18030兼容GBK,兼容GB2312,兼容ASCII,但是GB18030,GBK,GB2312这三个编码和ISO8859-1是不兼容的哦。
Big5

Big5是针对繁体中文的,广泛用于台湾香港等地。Big5包括1万3千多个繁体字,和GB2312类似,一个字符同样固定使用两个字节表示。在这两个字节中,第一个字节范围是10000001(十进制129) 到1111 1110(十进制254),第二个字节范围是0100 0000(十进制64) - 0111 1110(十进制126) 和1010 0001(十进制161)  - 1111 1110(十进制254)。Big5和GB18030,GBK,GB2312不兼容哈,如果已经理解了上文,其实你就能理解为什么Big5和GB的三个编码为什么不兼容了。

编码汇总
       我们简单汇总一下上面的内容。Ascii码是基础,一个字节表示,最高位设为0,其他7位表示128个字符。其他编码都是兼容Ascii的,最高位使用1来进行区分。西欧主要使用Windows-1252,使用一个字节,增加了额外128个字符。中文大陆地区的三个主要编码GB2312,GBK,GB18030,有时间先后关系,表示的字符数越来越多,且后面的兼容前面的,GB2312和GBK都是用两个字节表示,而GB18030则使用两个或四个字节表示。香港台湾地区的主要编码是Big5。
       如果文本里的字符都是Ascii码字符,那么采用以上所说的任一编码方式都是一样的,不会乱码。但如果有高位为1的字符,除了GB2312/GBK/GB18030外,其他编码都是不兼容的,比如,Windows-1252和中文的各种编码是不兼容的,即使Big5和GB18030都能表示繁体字,其表示方式也是不一样的,而这就会出现所谓的乱码。



作者: 大山哥哥    时间: 2019-2-27 23:19
希望对我的学生们学习乱码的时候有所帮助,谢谢各位同学的支持,大家能学会东西 就是我写文章最大的动力,我是大家的贤哥




欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2