前言
在我们上网或者抓包的过程中,偶尔会看到一些"乱码",比如下面这些
1、\346\234\254
2、%E6%9C%AC或者%B1%BE
常见地方有使用Wireshark抓包的时候,会出现第一种,在url中则经常看到的式第二种,下面我就来介绍下,这两种"乱码"的识别方法。
那些不得不说的编码
- ASCII码
这个估计是学过计算机都知道的一种编码,编码长度为8位(只使用后面7位,第一位规定为0),对应到编程语言中就是一个char类型。这是美国在19世纪60年代的时候为了建立英文字符和二进制的关系时制定的编码规范,它能表示128个字符,其中包括英文字符、阿拉伯数字、西文字符以及32个控制字符。它用一个字节来表示具体的字符,
- 扩展的ASCII码
原本的ASCII码对于英文语言的国家是够用了,但是欧洲国家的一些语言会有拼音,这时7个字节就不够用了。因此一些欧洲国家就决定,利用字节中闲置的最高位编入新的符号。比如,法语中的é的编码为130(二进制10000010)。这样一来,这些欧洲国家使 用的编码体系,可以表示最多256个符号。但这时问题也出现了:不同的国家有不同的字母,因此,哪怕它们都使用256个符号的编码方式,代表的字母却不一样。比如,130在法语编码 中代表了é,在希伯来语编码中却代表了字母Gimel (ג),在俄语编码中又会代表另一个符号。但是不管怎样,所有这些编码方式中,0—127表示的符号是一样的,不一样的只是128—255的这一段。这个问题就直接促使了Unicode编码的产生。
- Unicode符号集
世界上存在着多种编码方式,同一个二进制数字可以被解释成不同的符号。因此,要想打开一个文本文件,就必须知道它的编码方式,否则用错误的编码方式解读,就会出现乱码(数据的保存都是二进制的,读取的方式不同显示的结果也就不同,不要把后缀名等同于文件格式)。其实Unicode是一个符号集(世界上所有符号的符号集),而不是一种新的编码方式。
但是正因为Unicode包含了所有的字符,而有些国家的字符用一个字节便可以表示,而有些国家的字符要用多个字节才能表示出来。即产生了两个问题:第一,如果有两个字节的数据,那计算机怎么知道这两个字节是表示一个汉字呢?还是表示两个英文字母呢?第二,因为不同字符需要的存储长度不一样,那么如果Unicode规定用2个字节存储字符,那么英文字符存储时前面1个字节都是0,这就大大浪费了存储空间。
上面两个问题造成的结果是:
1)出现了unicode的多种存储方式,也就是说有许多种不同的二进制格式,可以用来表示unicode。
2)unicode在很长一段时间内无法推广,直到互联网的出现。
- UTF-8
联网的普及,强烈要求出现一种统一的编码方式。UTF-8就是在互联网上使用最广的一种unicode的实现方式。其他实现方式还包括UTF-16和UTF-32,不过在互联网上基本不用。重复一遍,这里的关系是,UTF-8是Unicode的实现方式之一。
UTF-8最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。
UTF-8的编码规则很简单,只有两条:
1)对于单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码。因此对于英语字母,UTF-8编码和ASCII码是相同的。
2)对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的unicode码。
- GBK/GB2312/GB18030
GBK和GB2312都是针对简体字的编码(GB=国标),只是GB2312只支持六千多个汉字的编码,而GBK支持1万多个汉字编码。而GB18030是用于繁体字的编码。汉字存储时都使用两个字节来储存。
对于我们程序员来说,最常打交道就是ASCII,UTF-8以及GB2312了,对于中文乱码往往也是针对UTF-8以及GB2312解编码的不同而出现的。UTF-8三个字节表示一个汉字,GB2312使用两个字节表示一个汉字,所以当两端编码方式不同的时候就会出现乱码了。
"乱码"分析
在上面我已经说了,乱码产生的原因就是收发两端的解编码方式不同而产生的,所以只需要采用同一种编码即可。在项目中一般都是统一为UTF-8,毕竟GB2312只是针对汉字而已
"乱码"的还原
上面Ctrl+C Ctrl+V了一大堆,下面就来实际说说怎么解"乱码",对于中文乱码一般都是转换为UTF-8,如果还是乱码那转换为GB2312。
乱码一:%E6%9C%AC
上面的"乱码"往往出现在URL中,因为一般来说,URL只能使用英文字母、阿拉伯数字和某些标点符号,不能使用其他文字和符号。比如,世界上有英文字母的网址“http://www.abc.com”,但是没有希腊字母的网址“http://www.aβγ.com”(读作阿尔法-贝塔-伽玛.com)。这是因为网络标准RFC 1738做了硬性规定:
"...Only alphanumerics [0-9a-zA-Z], the special characters "$-_.+!*'()," [not including the quotes - ed], and reserved characters used for their reserved purposes may be used unencoded within a URL."
“只有字母和数字[0-9a-zA-Z]、一些特殊符号“$-_.+!*'(),”[不包括双引号]、以及某些保留字,才可以不经过编码直接用于URL。”
而且为了区分每一个字节,URL编码还是用%分割开,所以%E6%9C%AC其实就是E69CAC,16进制表示的数据,按照UTf-8标准,3个字节表示一个汉字,解码过来就是"本",如果你使用GB2312解码,那就是一个乱码了,所以收发两端的解编码方式一定要相同!!!!
String utf_8 = "%E6%9C%AC"; String decode_utf_8 = URLDecoder.decode("%E6%9C%AC", "utf-8"); String decode_gb2312 = URLDecoder.decode("%E6%9C%AC", "gb2312"); System.out.println("utf-8:" + decode_utf_8); System.out.println("gb2312:" + decode_gb2312); //输出为: //utf-8:本 //gb2312:��
"本"的GB2312编码为%B1%BE,大家可以带入上面的代码中实验下,看看结果。
乱码二:\346\234\254
这是我在使用Wireshark抓包发现的乱码,其实他只是将二进制的显示格式换了下而已,就像十进制的10,用16进制为A,用8进制为12,这种3位代表一个字节,很容易猜出是8进制的编码。同理,"本"的16进制为E69CAC,将其转化为8进制就为346(E6)234(9C)254(AC),可以看出\346\234\254表示的也是"本"。
private static void f(String hex) { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); // 转化为字节流 ByteArrayInputStream inputStream = new ByteArrayInputStream( hex.getBytes()); int read = -1; // UTF-8 3个字节代表一个汉字 byte[] byte3 = new byte[3]; while ((read = inputStream.read()) > -1) { // \\是\的转义,如果是\代表后面3位是一个用8进制表示的3个字节 if (read == '\\') { try { inputStream.read(byte3); } catch (IOException e) { e.printStackTrace(); } // 将8进制转化为10进制 outputStream.write(Integer.parseInt(new String(byte3), 8)); } else { outputStream.write(read); } } String decodeMessage = null; try { // 采用UTF编码 decodeMessage = new String(outputStream.toByteArray(), "utf-8"); } catch (UnsupportedEncodingException e) { } System.out.println(decodeMessage); } f("\\346\\234\\254"); output:本
参考链接
http://www.myexception.cn/vc-mfc/1105596.html
http://www.360doc.com/content/14/0311/11/14423330_359516565.shtml#