ASCII编码
128个字符用7个位表示,计算机最小存储单位是byte,即8位。ASCII最高位是0,剩余7位表示字符。7位刚好表示0-127。
具体表示如下:
Ascii 码对美国是够用了,但对别的国家而言却是不够的,于是,各个国家的各种计算机厂商就发明了各种各种的编码方式以表示自己国家的字符,为了保持与Ascii 码的兼容性,一般都是将最高位设置为1。也就是说,当最高位为0时,表示Ascii码,当为1时就是各个国家自己的字符。
在这些扩展的编码中,在西欧国家中流行的是ISO 8859-1和Windows-1252,在中国是GB2312,GBK,GB18030和Big5,我们逐个来看下这些编码。
ISO 8859-1
ISO 8859-1又称Latin-1,它也是使用一个字节表示一个字符,其中0到127与Ascii一样,128到255规定了不同的含义。
Windows-1252
实际使用中更为广泛的是Windows-1252编码,这个编码与ISO8859-1基本是一样的,区别 只在于数字128到159,Windows-1252使用其中的一些数字表示可打印字符。一般使用中指的都是Windows-1252。
GB2312
GB2312标准主要针对的是简体中文常见字符,包括约7000个汉字,不包括一些罕用词,不包括繁体字。
GB2312固定使用两个字节表示汉字,在这两个字节中,最高位都是1,如果是0,就认为是Ascii字符。在这两个字节中,其中高位字节范围是0xA1-0xF7,低位字节范围是0xA1-0xFE。
GBK
GBK建立在GB2312的基础上,向下兼容GB2312,也就是说,GB2312编码的字符和二进制表示,在GBK编码里是完全一样的。
GBK增加了一万四千多个汉字,共计约21000汉字,其中包括繁体字。
GBK同样使用固定的两个字节表示,其中高位字节范围是0x81-0xFE,低位字节范围是0x40-0x7E和0x80-0xFE。
GB18030
GB18030向下兼容GBK,增加了五万五千多个字符,共七万六千多个字符。包括了很多少数民族字符,以及中日韩统一字符。
用两个字节已经表示不了GB18030中的所有字符,GB18030使用变长编码,有的字符是两个字节,有的是四个字节。
Big5
Big5是针对繁体中文的,广泛用于台湾香港等地。
Big5包括1万3千多个繁体字,和GB2312类似,一个字符同样固定使用两个字节表示。在这两个字节中,高位字节范围是0x81-0xFE,低位字节范围是0x40-0x7E和0xA1-0xFE。
汇总
Ascii码是基础,一个字节表示,最高位设为0,其他7位表示128个字符。其他编码都是兼容Ascii的,最高位使用1来进行区分。
西欧主要使用Windows-1252,使用一个字节,增加了额外128个字符。
中文大陆地区的三个主要编码GB2312,GBK,GB18030,有时间先后关系,表示的字符数越来越多,且后面的兼容前面的,GB2312和GBK都是用两个字节表示,而GB18030则使用两个或四个字节表示。
香港台湾地区的主要编码是Big5。
如果文本里的字符都是Ascii码字符,那么采用以上所说的任一编码方式都是一一样的。
但如果有高位为1的字符,除了GB2312/GBK/GB18030外,其他编码都是不兼容的,比如,Windows-1252和中文的各种编码是不兼容的,即使Big5和GB18030都能表示繁体字,其表示方式也是不一样的,而这就会出现所谓的乱码。
Unicode
Unicode 做了一件事,就是给世界上所有字符都分配了一个唯一的数字编号,这个编号范围从0x000000到0x10FFFF,包括110多万。但大部分常用字符都 在0x0000到0xFFFF之间,即65536个数字之内。每个字符都有一个Unicode编号,这个编号一般写成16进制,在前面加U+。大部分中文 的编号范围在U+4E00到U+9FA5。
Unicode只规定了每个字符的数字编号是多少。
编号与二进制的具体对应,需要UTF-32, UTF-16和UTF-8。
UTF-32
是字符编号的整数二进制形式,四个字节。每个字符都用四个字节表示,非常浪费空间,实际采用的也比较少。
UTF-16
UTF-16使用变长字节表示:
对于编号在U+0000到U+FFFF的字符 (常用字符集),直接用两个字节表示。
字符值在U+10000到U+10FFFF之间的字符(也叫做增补字符集),需要用四个字节表示。
UTF-16常用于系统内部编码,UTF-16比UTF-32节省了很多空间,但是任何一个字符都至少需要两个字节表示,对于美国和西欧国家而言,还是很浪费的。
UTF-8
UTF-8就是使用变长字节表示,每个字符使用的字节个数与其Unicode编号的大小有关,编号小的使用的字节就少,编号大的使用的字节就多,使用的字节个数从1到4个不等。
小于128的,编码与Ascii码一样,最高位为0。其他编号的第一个字节有特殊含义,最高位有几个连续的1表示一共用几个字节表示,而其他字节都以10开头。
UTF-8是兼容Ascii的,对大部分中文而言,一个中文字符需要用三个字节表示。
编码转换
各种编码之间可以借助Unicode编号进行编码转换。可以简化认为,每种编码都有一个映射表,存储其特有的字符编码和Unicode编号之间的对应关系,
编码转换的具体过程可以是,比如说,一个字符从A编码转到B编码,先找到字符的A编码格式,通过A的映射表找到其Unicode编号,然后通过Unicode编号再查B的映射表,找到字符的B编码格式。
乱码
计算机程序为了便于统一处理,经常会将所有编码转换为一种方式,比如UTF-8, 在转换的时候,需要知道原来的编码是什么,但可能会搞错,而一旦搞错,并进行了转换,就会出现这种乱码。
乱码恢复
"乱"主要是因为发生了一次错误的编码转换,恢复是要恢复两个关键信息,一个是原来的二进制编码方式A,另一个是错误解读的编码方式B。
恢复的基本思路是尝试进行逆向操作,假定按一种编码转换方式B获取乱码的二进制格式,然后再假定一种编码解读方式A解读这个二进制,查看其看上去的形式,这个要尝试多种编码,如果能找到看着正常的字符形式,那应该就可以恢复。
使用Java恢复乱码
Java中处理字符串的类有String,String中有我们需要的两个重要方法:
- public byte[] getBytes(String charsetName),这个方法可以获取一个字符串的给定编码格式的二进制形式
- public String(byte bytes[], String charsetName),这个构造方法以给定的二进制数组bytes按照编码格式charsetName解读为一个字符串。
将A看做GB18030,B看做Windows-1252,进行恢复的Java代码如下所示:
String str = "ÀÏÂí"; String newStr = new String(str.getBytes("windows-1252"),"GB18030"); System.out.println(newStr);
先按照B编码(windows-1252)获取字符串的二进制,然后按A编码(GB18030)解读这个二进制,得到一个新的字符串,然后输出这个字符串的形式。
实际中,我们可以写一个循环,测试不同的A/B编码中的结果形式,代码如下所示:
public static void recover(String str)
throws UnsupportedEncodingException{
String[] charsets = new String[]{"windows-1252","GB18030","Big5","UTF-8"};
for(int i=0;i<charsets.length;i++){
for(int j=0;j<charsets.length;j++){
if(i!=j){
String s = new String(str.getBytes(charsets[i]),charsets[j]);
System.out.println("---- 原来编码(A)假设是: "+charsets[j]+", 被错误解读为了(B): "+charsets[i]);
System.out.println(s);
System.out.println();
}
}
}
}
以上代码使用不同的编码格式进行测试,如果输出有正确的,那么就可以恢复。
不是所有的乱码形式都是可以恢复的,如果形式中有很多不能识别的字符如�?,则很难恢复,另外,如果乱码是由于进行了多次解析和转换错误造成的,也很难恢复。