最近遇到一个关于字符编码与解码的问题,使用GB2312保存了一个文件,然后使用vscode打开的时候,发现中文字符全是乱码了。为什么会出现这个问题?研究了一下编码与解码。
文件在计算机上存储的都是二进制。顾名思义,编码就是把一个字符编码成二进制码存起来的方式,而解码就是把这个二进制码按照原本编码的规则还原成原来的字符。
我们经常使用的ASCII码,是上个世纪60年代美国制定的一套字符编码,它规定了英语字符与二进制位之间的关系,一直沿用至今。ASCII 码一共规定了128个字符的编码。ASCII使用一个字节来进行编码,一个字节有8个bit位,ASCII只使用了后面的7个bit位,最前面的一个bit位使用0填充。
一些欧洲国家发现ASCII编码的128个字符不能表示他们的语言的所有的字符,所以他们决定启用最前面的一位,这样一来,就可以编码256个字符了,比以前又多了128个字符可以使用。
但是又有问题出现了,不同的国家的字母不一样,他们启用最高位来进行编码,不同的语言有不同的编码方式,导致了同样的编码在不同的国家代表的字符不一样;例如法语中130代表é,但是在希腊语中代表的是ג。注意由于各个国家都是在美国制定的标准上来扩充ASCII的,他们都保留了美国人制定的标准,也就是说所有的字符编码中,0-127表示的符号是一样的,128-256表示的符号在各个国家制定的编码表中是不一样的。对于中文来说,256个字符根本不够用,常用的汉字就有5000多个,所以没有办法使用ASCII进行编码。
这时候为了统一编码,并且能够表示全世界所有的符号,就出现了Unicode编码。它是有文字符号的国际标准编码,使用四个字节为每个字符编码。但是每个字符都使用四个字节来表示,会出现空间浪费,比如,字符a使用ASCII编码只需要一个字节就能够存储,但是如果使用Unicode编码,会浪费三个字节的空间。
本着节约的原则,就出现了UTF-8编码,UTF-8编码是一种可变长度编码,它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。UTF-8编码也能够表示全世界所有的字符。
UTF-8 的编码规则很简单,只有二条:
1)对于单字节的符号,字节的第一位设为0
,后面7位为这个符号的 Unicode 码。因此对于英语字母,UTF-8 编码和 ASCII 码是相同的。
2)对于n
字节的符号(n > 1
),第一个字节的前n
位都设为1
,第n + 1
位设为0
,后面字节的前两位一律设为10
。剩下的没有提及的二进制位,全部为这个符号的 Unicode 码。
下表总结了UTF-8编码规则,字母x
表示可用编码的位。(四条编码规则)
上面弄清楚了ASCII,Unicode,UTF-8编码之间的关系。可以发现其实某一个字符的UTF-8编码的二进制其实还是保存的对应的Unicode编码的二进制。
其实在计算机内存中,统一使用Unicode编码,当需要保存到硬盘或者需要传输的时候,会进行相应的转换。存储在磁盘上的都是二进制数据,只不过编码方式可能不一样,有可能是UTF-8或者GB2312等。
当我们保存文件的时候,如果选择的是UTF-8编码进行保存,那么存储在计算机内存中的Unicode编码的二进制就会根据上面介绍的UTF-8编码规则被编码成UTF-8形式的二进制存储到磁盘上。
当我们打开文件的时候,假设存储文件的时候使用的是UTF-8进行编码的,那么在打开文件进行解码的时候,就会根据上面介绍的UTF-8编码规则进行解码,得到Unicode编码的二进制,然后查找Unicode码表,找到对应的字符进行显示。
回到刚开始遇到的问题。当我们使用vscode打开一个实际上是GB2312编码的文件的时候,vscode默认是使用utf-8来进行解码的。分析一下出现乱码的原因:
就拿中文“汉”字来说,它的GB2312编码是“BABA”,存储在磁盘上的二进制是1011 1010 1011 1010。如果使用vscode直接打开,vscode会直接使用UTF-8进行解码,vscode会根据UTF-8的编码规则去解码,它会发现1011 1010 1011 1010根本不能对应上UTF-8的任何一条编码规则,这时候vscode就不能识别,就会出现乱码了。
当然有些汉字,例如“联通”的GB2312编码是”C1AA CDA8“ ,恰好符合UTF-8的编码规则(这只是一种巧合),存储在磁盘上的二进制是:
同样vscode使用UTF-8去解码的时候,发现它对应于UTF-8第二条编码规则。这时候就会提取出Unicode编码的二进制,把第一个字节的110和第二个字节的10去掉,我们就得到了“00001 101010”,从低字节开始进行各位对齐,补上前导的0,就得到了“0000 0000 0110 1010”的Unicode二进制编码,这是UNICODE的006A;同理,把第三个字节的110和第四个字节的10去掉,得到了“01101 101000”,同样从低字节开始对齐各位,得到了“0000 0011 0101 1000”的Unicode二进制编码,这是UNICODE的0368。
下一步会去查找Unicode编码表,看这些Unicode编码对应的字符是什么,006A对应的是小写字母“j”,0368什么也不是,这个时候就会出现乱码了。
对应于vscode使用UTF-8解码其它的GB2312的字符,原理是类似的,由于不能够准确解析,就会出现乱码。如果想解决这个乱码问题,可以先使用GB2312打开该文件,然后再使用UTF-8重新保存该文件就可以了。参考https://www.cnblogs.com/wangwenhui/p/11987569.html
来源:https://www.cnblogs.com/wangwenhui/p/12013532.html