源自:http://blog.csdn.net/WebDynpro/archive/2006/10/09/1326614.aspx
在java中的字符均采用字符集UCS-2(编码为UTF-16,UCS-2可以看作所有字符集的超集
.其他字符集都可以映射到UCS-2).而外部编码却很多种.字符有两个流向:从系统流向jvm中
运行的程序,由jvm中运行的程序流向系统.在这过程中都有可能出现乱码.
因而这两个流向均存在编码和解码的问题.
先明确几个概念,编码和解码(encode/decode),在java中解码是指把某种编码的字符
转为UCS-2字符集中对应的字符,编码是指把UCS-2字符集中的字符转为某种编码的字符.乱
码,乱码分两种,一种是由于系统没有使用正确的编码无法正确显示,一种是由于字符处理错
误导致产生不可识别的内容.虽然看上去的现象相同,但两者是不同的,前者可以通过使用合
适的编码显示,本身是没有错误的,后者则不可.我在这里所说的乱码主要指第二种.
java默认的输入输出编码为操作系统的默认编码.因此我们处理本地编码的文件很少遇
到乱码的问题.
我们可以在输入和输出时选择编码方式.以正确处理其他编码的文件.
从外部的字符以原来的编码读入可以正确地用UCS-2字符集中的字符表示.因此可以在
程序中正常处理.比如有一个日文Shift-jis编码的文件.用如下方式即可
BufferedReader in = new BufferedReader(new InputStreamReader(new FileInput
Stream(inFile), "SJIS"));
如果以其他编码方式读入,显然得到的数据是乱码的,而且这个过程是不可逆的,那上面
这个例子改一下:
BufferedReader in = new BufferedReader(new InputStreamReader(new FileInput
Stream(inFile), "GB2312"));//乱码出现的源头在这一句
String line = in.readLine();
System.out.println(new String(line.getBytes("GB2312"), "SJIS"));//将输出乱
码,Shift-JIS编码的文件以GB2312方式读入时无法正确映射到UCS-2上,因为Shift-JIS的很
多字符的编码在GB2312中是不存在的,用getBytes("GB2312")读出的字节流和输入时的字节
流已经不同,无法再正确地转为SJIS.
但是如果把GB2312编码方式改为8859_1(ISO-8859-1,8位编码,兼容我们常用的ASCII字
符集的编码),却是可以的.这个特殊情况的出现是有原因的.因为ISO-8859-1以字节为单位
编码,而每个字节其编码中都有对应字符,从ISO-8859-1映射到UCS-2上,再从UCS-2编码为I
SO-8859-1,这两个过程是互逆的对应的,这个特性有个特殊的应用,后文中将提到.我们在网
上常见到用getBytes("ISO-8859-1")这种方式来获取乱码前的字节流,但这应该算是一个特
例,只能用于以ISO-8859-1编码方式的错误读入的字符流.
同样的,从UCS-2字符集到其他编码如果采用了错误的编码方式也会造成乱码,且过程不
可逆,甚至包括ISO-8859-1编码.即对于
String myStr = "字符串"'
String convertedStr = new String(myStr.getBytes("ISO-8859-1"), "ISO-8859-1
");
myStr.equals(convertedStr)将为false.
故而我们可以知道,对于java的字符乱码,产生的源头有三个:
一.从源字符流到UCS-2的转换过程;
这是因为原字符流以错误的编码转换为UCS-2,而原编码和转换用的编码不兼容,这一转换
过程完成后不可逆,即以错误编码得到的字符无法还原成正确的编码.例如前面把Shift-JI
S编码的文件以GB2312格式读入.
看看下面的例子:
假设有GB2312的字符保存在文件inFile中
1.BufferedReader in = new BufferedReader(new InputStreamReader(new FileInp
utStream(inFile), "GB2312"));
String line = in.readLine();//得到正确的字符
2.BufferedReader in = new BufferedReader(new InputStreamReader(new FileInp
utStream(inFile), "GBK"));
String line = in.readLine();//得到正确的字符,GBK完全兼容GB2312编码,GB231
2编码的字符完全可以以GBK读入,反之则可能出现部分乱码,因为GBK中存在GB2132没有定义
的字符
3.BufferedReader in = new BufferedReader(new InputStreamReader(new FileInp
utStream(inFile), "SJIS"));
String line = in.readLine();//得到乱码字符,GB2312与Shift-JIS不兼容
二.从UCS-2到字符流的转换过程;
这是由于UCS-2表示的字符在要转向的编码中不存在对应的字符,导致转换出错,得到乱码
.如
String chars = "我们";//"我们"采用的是Java内部的字符集UCS-2
String chars_converted = new String( chars.getBytes("ISO-8859-1"), "UTF-16");
//getBytes()将UCS-2字符编码为英文字符集ISO-8859-1,但ISO-8859-1并不包含"我们"这
两个字符,所以错误地编码了,此过程同样不可逆,我们无法用java提供的api将其还原成原
来的字符
看看下面的例子:
String chars = "我们";
String chars_enGB2312 = new String(chars.getBytes("GB2312"), "GB2312");
System.out.println( chars.equals(chars_enGB2312));//true!
String chars_enUTF8 = new String(chars.getBytes("UTF-8"), "UTF-8");
System.out.println( chars.equals(chars_enUTF8));//true!
String chars_enSJIS = new String(chars.getBytes("SJIS"), "SJIS");
System.out.println( char.equals(chars_enSJIS));//false!日文Shift-JIS中没有"们
"字,如果去掉"们"字将为true
以上例子只为说明UCS-2到其他编码的转换,实际上并不推荐上面这种做法,因为UCS-2是差
不多是所有字符集的超集,从超集到子集的转换,除非能保证所用字符在子集上,否则很容易
出现乱码.
实际问题中第一种情况最为常见,第二种是多数是字符从外部读入保存在String中进一步
处理是发生,实际上,我认为getBytes()编码后得到的字节流只应该用其超集再编码.
三.从java程序中向系统输出字符流
向系统中写出字节流的时候面临的问题和读入时是一样的,如果我们处理的不是使用系统
默认编码的字符,则输出时应该注意以合适的编码输出,否则会产生不可识别的乱码.
对于iso-8859-1编码,如前面提到的,以ISO-8859-1编码读入并表示为UCS-2字符,然后再以
ISO-8859-1编码将得到原始编码的字符流.即iso-8859-1到UCS-2,再由UCS-2到iso-8859-1
是可逆的.可以用来在不支持其他字符集的机器上处理和存储其他编码的字符(当然不能显
示),而且也可输出原编码的字节流.
假设inFile为GB2312编码的文件,
BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStr
eam(inFile), "ISO-8859-1"));
String line = in.readLine();
String out = new String(line.getBytes("ISO-8859-1"), "GB2312");//得到正确的GB
2312编码字符
Note:1.utf-8采用2/3混编的方式。目前容纳的汉字范围小于gbk编码。
2.按照GBK18030、GBK、GB2312的顺序,3种编码是向下兼容.
3.utf-8只兼容iso-8859-1,对于其他编码方式并不兼容.很多文章提到utf编码兼容很
多其他编码的提法其实只是表明,其他编码中的字符在utf-8中有对应的字符,但他们的位置
并不相同.而且存在其他编码有的字utf没有的情况.如1中提到的gbk.
呼~总算写完了.网上很多写java乱码问题的很多都写得不清不楚,错漏百出,自己研究
了一下.不过对于服务器端编程时容器代来的乱码的问题似乎比较难深入系统分析,各个服
务器之间也似有不同.