字符编码与解码,乱码原因分析
最近遇到一个关于字符编码与解码的问题,使用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编码。unicode是一个标准,也可以说是世界上的语言字符和数字映射的一种标准。它没有限制字符的数量,但是可能这个标准规定的映射只是映射了一部分字符。网上说的Unicode编码占二个字节或者四个字节都是有问题的(目前来说,可能占两个字节(使用UCS-2),也可能占四个字节(使用UCS-4)关于UCS-2和UCS-4感兴趣可以了解一下)。Unicode 只是一个符号集,它只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储,也就是说没有指定存储的时候使用多少个字节。
Unicode是字符集,为每一个「字符」分配一个唯一的 ID(学名为码位 / 码点 / Code Point)。简单的理解就是,拿中文来说,常用的汉字有5000多个,那么就是给每一个汉字一个唯一的编号(比如说1-5000),这就是所谓的unicode字符集,对于其他语言的字符,也有一个唯一的编号。
UTF-8编码是一种可变长度编码,它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。UTF-8编码也能够表示全世界所有的字符。UTF-8 是 Unicode 的实现方式之一。
UTF-8是编码规则,将「码位」转换为字节序列的规则(编码/解码 可以理解为 加密/解密 的过程)。由于Unicode只是定义了每一个字符对应的编号,但是计算机只能处理二进制,所以存储以及内存中的字符必须被编码成二进制形式,这时候UTF-8就起作用了。UTF-8定义了一套规则,能够将Unicode给每一个字符的编号,转换成计算机能够理解的二进制。这样计算机能够理解。
UTF-8 的编码规则很简单,只有二条:
1)对于单字节的符号,字节的第一位设为0
,后面7位为这个符号的 Unicode 码。因此对于英语字母,UTF-8 编码和 ASCII 码是相同的。
2)对于n
字节的符号(n > 1
),第一个字节的前n
位都设为1
,第n + 1
位设为0
,后面字节的前两位一律设为10
。剩下的没有提及的二进制位,全部为这个符号的 Unicode 码。
下表总结了UTF-8编码规则,字母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 第四条编码规则
上面弄清楚了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的编码规则(这只是一种巧合),存储在磁盘上的二进制是:
1100 0001 1010 1010 第一、二字节
1100 1101 1010 1000 第三、四字节
同样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
参考链接:http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html
上一篇: JavaScript原型
下一篇: 开放源代码的加密工具GPG使用方法