转换方法说起来比较容易,对于UTF系列或者是ISO-8859-1这种被兼容的编码,可以通过计算和Unicode数值直接进行转换(实际可能也是查表),而对于系统遗留下来的ANSI编码,则只能通过查表的方式进行,微软把这种映射表称为Code Page(代码页),并按编码进行分类编号,比如我们常见的cp936就是GBK的代码页,cp65001就是UTF-8的代码页。下图是微软官网查到的GBK->Unicode映射表(目测不全),同理还应有反向的Unicode->GBK映射表。
有了代码页,就可以很方便的进行各种编码转换了,比如从GBK转换到UTF-8,只需要先按照GBK的编码规则对数据按字符划分,用每个字符的编码数据去查GBK代码页,得到其Unicode数值,再用该Unicode去查UTF-8的代码页(或直接计算),就可以得到对应的UTF-8编码。反过来同理。注意:UTF-8是Unicode的标准实现,它的代码页中包含了所有的Unicode取值,所以任意编码转换到UTF-8,再转换回去都不会有任何丢失。至此,我们可以得出一个结论就是,要完成编码转换工作,最重要的是第一步要成功的转换到Unicode,所以正确选择字符集(代码页)是关键。
理解了转码丢失问题的本质后,我才突然明白JSP的框架为什么要以ISO-8859-1去解码HTTP请求参数,导致我们获取中文参数的时候不得不写这样的语句:
String param = new String(s.getBytes("iso-8859-1"), "UTF-8");
因为JSP框架接收到的是参数编码的二进制字节流,它不知道这究竟是什么编码(或者不关心),也就不知道该查哪个代码页去转换到Unicode。然后它就选择了一种绝对不会产生丢失的方案,它假设这是ISO-8859-1编码的数据,然后查ISO-8859-1的代码页,得到Unicode序列,因为ISO-8859-1是按字节编码的,而且不同于ASCII的是,它对0 ~ 255空间的每一位都进行了编码,所以任意一个字节都能在它的代码页中找到对应的Unicode,若再从Unicode转回原始字节流的话也就不会有任何丢失。它这样做,对于不考虑其他语言的欧美程序员来说,可以直接用JSP框架解码好的String,而要兼容其他语言的话也只需要转回原始字节流,再以实际的代码页去解码一下就好。
我对Unicode以及字符编码的相关概念阐述完毕,接下来用Java实例来感受一下。
三、实例分析 1.转换到Unicode——String构造方法public class Test { main(String[] args) throws IOException { [] gbkData = {(byte)0xc4, (byte)0xe3, (byte)0xba, (byte)0xc3}; [] big5Data = {(byte)0xa7, (byte)0x41, (byte)0xa6, (byte)0x6e}; //构造String,解码为Unicode String strFromGBK = new String(gbkData, "GBK"); String strFromBig5 = new String(big5Data, "BIG5"); //分别输出Unicode序列 showUnicode(strFromGBK); showUnicode(strFromBig5); } showUnicode(String str) { for (int i = 0; i < str.length(); i++) { System.out.printf("\\u%x", (int)str.charAt(i)); } System.out.println(); } }
运行结果如下图
从结果可以发现,只要指定了正确的字符集(代码页),String就可以解码出正确的Unicode,最后可以试试println("\u4f60\u597d"),输出的就是“你好”。
2.Unicode转换到各种编码——getBytesString拥有了Unicode序列,想要转换到其它编码就易如反掌了,根据你参数指定的字符集,去相应的代码页查找就可以转换过去了,当然如果该字符集不支持某字符(也就是没有这条Unicode记录),那就会导致编码丢失,再也不能还原到原来的Unicode序列了。
这里,我们和第1节的做法相反,我们把Unicode序列转换到其它各种编码,如下所示。
public class Test { main(String[] args) throws IOException { //字符串"你好" String str = "\u4f60\u597d"; //转换到各种编码 showBytes(str, "GBK"); showBytes(str, "BIG5"); showBytes(str, "UTF-8"); } showBytes(String str, String charset) throws IOException { for (byte b : str.getBytes(charset)) System.out.printf("0x%x ", b); System.out.println(); } }
运行结果如下图
可以发现,由于String掌握了Unicode码,要转换到其它编码so easy!
3.以Unicode为桥梁,实现编码互转有了上面两部分的基础,要实现编码互转就很简单了,只需要把他们联合使用就可以了。先new String把原编码数据转换为Unicode序列,再调用getBytes转到指定的编码就OK。
比如一个很简单的GBK到Big5的转换代码如下