中文乱码是开发中常见的问题,一般情况下出现中文乱码是因为对 中文字符 的编码方式和解密方式不一致导致的,这种情况下,只要设置统一的字符编码方式就可以解决或者避免出现乱码问题。但在项目开发中,偶尔会出现不管怎么设置编码方式,都不能正确恢复乱码的问题。上次正好遇到了这样一个中文乱码问题,在解决问题的过程中,了解了字符编码的历史,常见编码字符集的由来,对解决中文乱码问题起到了很大的帮助(对字符编码以及各种常见字符集的分析总结,会在后面的博客中发出来)。
对于切换编码方式无法解决的中文乱码问题,常见的原因是:一段使用A编码方式的中文,在传输、存储的过程中被错误的使用B编码方式编码,再次展示的时候使用A编码方式解码,最终出现了很奇怪的乱码问题。
例如: 你好 这个中文单词,最开始使用 UTF-8 编码,那么其16进制编码为:E4BDA0 E5A5BD,然而在解码的时候,错误的使用了 GB18030 编码方式,也就是说,这个时候解码程序以GB18030字符集解码 E4BD A0E5 A5BD 这段16进制,得到的结果是:浣犲ソ 。很明显,这个时候乱码了。这时,在传输、存储这段乱码文本的时候,使用了 UTF-8 编码方式来进行编码,也就是对 浣犲ソ 这段文本进行编码,得到16进制编码为:E6B5A3E78AB2E382BD20 ,然后数据库或文件里存储的内容就变成了 E6B5A3E78AB2E382BD20。
对于 E6B5A3E78AB2E382BD20 这段16进制代码,如果使用 UTF-8 编码方式来解码的话,得到的是: 浣犲ソ ,乱码了。而如果使用 GB18030字符集 来解码的话,得到的是: 娴g姴銈?? 这样的乱码。不管使用其他的各种编码字符集进行解码,得到的结果都是一样——各种各样的乱码。
从乱码的过程可以看出,关键的一步在于第一次解码的时候,也就是使用 GB18030字符集解码的时候,乱码了,后面就是按照乱码文本进行存储,就算是用存储时的字符集解码,得到的依然是乱码文本。
这个时候怎么办呢?瞎猜。
尝试各种常见的字符集,先推测最开始用什么字符集编码的,后来又用的什么字符集解码的,对各种可能都尝试一遍,看哪种组合的结果能推导出正确的中文文本,就可以知道如果恢复了。
自己写了一个恢复中文乱码的程序,成功的恢复了乱码的中文文本,并在后续的乱码问题修复中多次使用,贴出来供大家参考一下。
package com.zy.test.encode;import java.io.UnsupportedEncodingException;public class EncodeTest {private static String[] charsetArr = {"UTF-8","GB18030","GB2312","GBK","Windows-1252","ISO8859-1"};public static void testAllCharset(String text) throws UnsupportedEncodingException {if (text == null || text.length() == 0) {System.out.println("文本不能为空");return;}System.out.println("假设当前编码假设原始编码 编码后的内容");printSeparator();for (String curCharset : charsetArr) {byte[] btArr = text.getBytes(curCharset);for (String originCharset : charsetArr) {if (originCharset.equals(curCharset)) {continue;}String encodeText = new String(btArr,originCharset);printTable(curCharset, originCharset, encodeText);}printSeparator();}}private static void printSeparator() {System.out.println("--------------------------------------------------------");}private static void printTable(String curCharset, String originCharset, String encodeText) {System.out.print(curCharset);for (int i = 0; i < 15 - curCharset.length(); i++) {System.out.print(" ");}System.out.print("|" + originCharset);for (int i = 0; i < 16 - originCharset.length(); i++) {System.out.print(" ");}System.out.println("| " + encodeText);}public static void main(String[] args) throws UnsupportedEncodingException {//测试乱码testAllCharset("浣犲ソ");}}执行结果如下:
假设当前编码假设原始编码 编码后的内容--------------------------------------------------------UTF-8 |GB18030 | 忙碌聥猫炉聲UTF-8 |GB2312 | 忙碌�猫炉�UTF-8 |GBK | 忙碌聥猫炉聲UTF-8 |Windows-1252| 测试UTF-8 |ISO8859-1| æµÂè¯Â--------------------------------------------------------GB18030|UTF-8| �0�3�0�8�0�1���0�4�0�1GB18030|GB2312 | �0�3�0�8�0�1è�0�4�0�1GB18030|GBK | �0�3�0�8�0�1è�0�4�0�1GB18030|Windows-1252| �0Š3�0…8�0‚1¨¨�0…4�0ƒ1GB18030|ISO8859-1| 030 801¨¨0 401--------------------------------------------------------GB2312 |UTF-8| ???��??GB2312 |GB18030 | ???è??GB2312 |GBK | ???è??GB2312 |Windows-1252| ???¨¨??GB2312 |ISO8859-1| ???¨¨??--------------------------------------------------------GBK|UTF-8| ???��??GBK|GB18030 | ???è??GBK|GB2312 | ???è??GBK|Windows-1252| ???¨¨??GBK|ISO8859-1| ???¨¨??--------------------------------------------------------Windows-1252|UTF-8| �?�?Windows-1252|GB18030 | 娴?璇?Windows-1252|GB2312 | 娴?璇?Windows-1252|GBK | 娴?璇?Windows-1252|ISO8859-1| æµ?è¯?--------------------------------------------------------ISO8859-1 |UTF-8| 测试ISO8859-1 |GB18030 | 娴嬭瘯ISO8859-1 |GB2312 | 娴�璇�ISO8859-1 |GBK | 娴嬭瘯ISO8859-1 |Windows-1252| 测试--------------------------------------------------------从执行结果可以看出,只有在 假设当前编码 为 ISO9959-1 ,假设原始编码 为 UTF-8 时,正确的恢复了乱码的中文文本。由此可以得出,该乱码最开始使用 UTF-8 编码,之后错误的使用了 ISO8859-1 字符集进行解码,并按照错误解码得到的二进制进行存储、传输(实际上大多数的中文乱码场景都是这样)。
需要注意的是,这个乱码恢复代码仅适用于 被错误解码一次 的情况,如果有多次被错误解码,那么恢复起来就需要尝试