小伙伴们coding路上有没有遇到过这些东西:
"�怨�蝚�" , "åˆ«è¸©ç™½å¥ " , "??’וˆ?°ו÷•ט¾¹ח₪„ח?";?V(pou3)哿(ge3)?啵▂in)??guo)?{(nan)
懵,什么东东啊?干啥子了??!
如果你稍微有点经验就会知道,这个就是传说中的乱码,简单来说,当编码和解码时用了不同或者不兼容的字符集就会出现乱码。那编码或者字符集又是个神马东西呐,为何如此神奇?那就拿好小板凳听我娓娓道来吧
字符集的作用
我们在计算机屏幕上看到的是实体化的文字,而在计算机存储介质中存放的实际是二进制的比特(byte)流。简单来说字符集就规定了某个文字对应的二进制数值存放方式(编码)和某串二进制数值代表了哪个文字(解码)的转换关系。正因为字符和字节之间有着这种固定的对应关系,所以我们的文件在以相同的字符集传递的情况之下才不会出现乱码。
字符集标准
字符集有很多规范和标准,最初制定时并不会意识到这将会是以后全球普适的准则,或者处于组织本身利益就想从本质上区别于现有标准。于是,就产生了那么多具有相同效果但又不相互兼容的标准了。
最早是美国人使用电脑,所以最初的字符集是ASCII,使用7位bit表示一个字符,总共能表示128种字符。后来,随着欧洲各个国家的计算机普及,人们发现ASCI码I对应的128个字符不能够满足需求了,因为欧洲各个国家都有一些特殊的字符。所以ASCII进行了扩容,使用8位bit表示一个字符,总共就能表示256种字符。
而当中国开始普及计算机后,发现256中字符根本没办法满足咱们中国人的日常需求,中华文化上下五千年,汉字的数量多了去了,一个字节根本没法对应一个字符。所以只能继续扩容,两个字节对应一个字符,所以后续的GB2312、GBK等等字符集就都被提出来了。
正因为世界上有各种各样的国家、民族需要使用计算机,各个国家地区都会根据自己的文化特色创造出相对应的字符集标准。
同一个字在多种字符集下对应的16进制和二进制对比
上述是同一个字在三种字符集下所对应的16进制和2进制数据,我们可以发现同样的一个字,在不同的字符集下对应的16进制和2进制数据完全不同。这个字就是我们平常用来形容一个人特别厉害的一个字--------"屌"!
为什么会发生乱码?
刚刚我们说到乱码的出现是因为:编码和解码时用了不同或者不兼容的字符集。对应到真实生活中,就好比是一个英国人为了表示祝福在纸上写了bless(编码过程)。而一个法国人拿到了这张纸,由于在法语中bless表示受伤的意思,所以认为他想表达的是受伤(解码过程)。这个就是一个现实生活中的乱码情况。
在计算机科学中一样,一个用UTF-8编码后的字符,用GBK去解码。由于两个字符集的字库表不一样,同一个汉字在两个字符表的位置也不同,最终就会出现乱码。
在UTF-8字符编码下(一个汉字对应三个字节),"很屌"这两个字对应的16进制字符串
于是我们得到了 E5BE88E5B18C 这么一串数值。而显示时我们用GBK解码进行展示(一个汉字对应两个字节),通过查表我们获得以下信息:
以上就是"很屌"这两个字在UTF-8字符集下进行编码,然后在GBK下进行解码,就变成了"寰堝睂"。不仅字符变得不认识了,甚至连字数都发生了变化。有没有觉得很屌的样子?
乱码后的字符串还原
要从乱码字符中反解出原来的正确文字需要对各个字符集编码规则有较为深刻的掌握。但是原理很简单,这里用最常见的UTF-8被错误用GBK展示时的乱码为例,来说明具体反解和识别过程。
第一步 编码
假设我们在页面上看到 寰堝睂 这样的乱码,而又得知我们的服务器当前使用GBK编码。那么第一步我们就能先通过GBK把乱码编码成二进制表达式。
第二步 解码
由于乱码之前的编码字符集是UTF-8,所以我们再使用UTF-8将二进制字节数组解码成字符串,这时候我们就能够得到这串字符串原本的样子,具体流程如下图所示
几个常见名词的介绍
ASCII码表
一开始最多只能表示128个字符,经过拓展后最多能表示256个字符。
Unicode
Unicode是国际组织制定的可以容纳世界上所有文字和符号的字符编码方案,相当于一个最大的字库。Unicode用数字0-0x10FFFF来映射这些字符,最多可以容纳1114112个字符。
既然Unicode包含全世界所有的文字和字符的话,那么为什么全世界不统一使用Unicode字库表来进行编码和解码呢?而非要弄出这么多各种各样的字符集出来呢?
因为如果全世界都统一使用Unicode的话,一个字符就要对应三个字节,对于美国人和欧洲人来说,他们使ASCII的话每个字符只需要对应一个字节。这样所造成的后果就是,本来美国人使用ASCII的时候一个U盘能存放1500篇文章,而改成使用Unicode之后U盘只能存放500篇文章了。所以全世界统一使用Unicode字库表并不现实。
UTF-8
UTF-8是一个当今接受度最广的字符集编码,但是它只涵盖了Unicode字库中的一小部分,Unicode的编号从 0000开始一直到 10FFFF 共分为16个Plane,每个Plane中有65536个字符。而UTF-8则只实现了第一个Plane,所以这也造成了它在某些场景下对于特殊字符的处理困难。
总结
Unicode:包容万国,优点是字符->数字的转换速度快,缺点是占用空间大。
UTF-8:精准,对不同的字符用不同的长度表示,优点是节省空间,缺点是:字符->数字的转换速度慢,因为每次都需要计算出字符需要多长的Bytes才能够准确表示。
所以一般在内存中使用的编码是unicode,用空间换时间,为了快。因为程序都需要加载到内存才能运行,因而内存应该是尽可能的保证快。但硬盘中或者网络传输用utf-8,网络I/O延迟或磁盘I/O延迟要远大与utf-8的转换延迟,而且I/O应该是尽可能地节省带宽,保证数据传输的稳定性。因为数据的传输,追求的是稳定,高效,数据量越小数据传输就越靠谱,于是都转成utf-8格式的,而不是unicode。