转自:http://blog.163.com/comfort_122/blog/static/489044092010715103354256/
从本文开始阐述我对java中文乱码问题的一些心得。希望这个系列文章能对被java中文编码问题困扰的朋友提供一点帮助。
文件编码与系统环境
在这个命题里面,我将关注与java源码文件的编码与操作系统自身的编码对于javac编译产生的影响。
首先简单介绍一下将要测试的编码格式,UTF8:如果你是在多语言环境下开发,这个是首选编码格式,它以三个字符表示大部分的中文字符。GB18030:兼容GBK并在其基础上扩展了大量字符,用2/4个字符表示一个汉字。GBK:中文编码国家规范,两个字符表示中文字符,windows系统的内码,包含所有常用汉字,如果是在中文环境下开发应该选择此编码。GB2312:早年教科书上的国家标准(不晓得现在还是不是),包含汉字6763个,两个字符表示中文字符。即 GB18030 >= GBK > GB2312。
现在开始设定一个测试环境,用测试结果来说明问题。[由于将测试的字符"硚"是超过GB2312但包含在GBK中的(正常的中文字符真的很难超过GBK的范围),所以部分测试变量GB18030和GBK可以认为是一致的。]
环境:Linux Red Hat 5 ;
变量:文件编码格式(UTF-8、GB18030、GB2312),系统环境LANG(en_US.UTF-8、zh_CN.GB18030、zh_CN.GBK、zh_CN.GB2312)。
(文件编码格式可以用EditPlus等文本编辑器另存为的方式修改,系统环境可以 vi /etc/sysconfig/i18n ; source /etc/sysconfig/i18n [全局] 或 修改 .bash_profile文件添加 export LANG=en_US.UTF-8 [当前用户] 或 直接在控制台执行 export LANG=en_US.UTF-8 [仅此控制台])。
测试代码 TestFileEncoding.java:
import java.io.UnsupportedEncodingException;
public class TestFileEncoding {
/**
* 把字节数组转换成16进制字符串
*
* @param bArray
* @return
*/
public static final String bytesToHexString(byte[] bArray) {
StringBuffer sb = new StringBuffer(bArray.length);
String sTemp;
for (int i = 0; i < bArray.length; i++) {
sTemp = Integer.toHexString(0xFF & bArray[i]);
if (sTemp.length() < 2)
sb.append(0);
sb.append(sTemp.toUpperCase());
}
return sb.toString();
}
public static void main(String[] args) throws UnsupportedEncodingException {
String text = "硚";
System.out.print(text.getBytes("UTF-8").length + "\t");
System.out.println(bytesToHexString(text.getBytes("UTF-8")));
System.out.print(text.getBytes("GBK").length + "\t");
System.out.println(bytesToHexString(text.getBytes("GBK")));
System.out.print(text.getBytes("GB2312").length + "\t");
System.out.println(bytesToHexString(text.getBytes("GB2312")));
}
}
当不停的变换变量的值,然后输出byte长度和其哈希值(相比在控制台打印出字符来看乱不乱[将受到控制台编码的影响],这种看字节长度和16进制值的方式更为准确),列出测试结果如下表:
文件编码 | 系统环境 | 字符串编码 | 字符串长度 | 字符串Hex值 | 取值是否正确 | |
UTF-8 | en_US.UTF-8 | UTF-8 | 3 | E7A19A | 是 | |
GBK | 2 | B37E | 是 | |||
GB2312 | 1 | 3F | 否 | |||
UTF-8 | zh_CN.GB18030 | UTF-8 | 6 | E7BAADEFBFBD | 否 | |
GBK | 3 | E7A13F | 否 | |||
GB2312 | 3 | E7A13F | 否 | |||
UTF-8 | zh_CN.GBK | UTF-8 | javac编译时异常:String literal is not properly closed by a double-quote | 否 | ||
GBK | 否 | |||||
GB2312 | 否 | |||||
UTF-8 | zh_CN.GB2312 | UTF-8 | 否 | |||
GBK | 否 | |||||
GB2312 | 否 | |||||
GB18030 | en_US.UTF-8 | UTF-8 | 4 | EFBFBD7E | 否 | |
GBK | 2 | 3F7E | 否 | |||
GB2312 | 2 | 3F7E | 否 | |||
GB18030 | zh_CN.GB18030 | UTF-8 | 3 | E7A19A | 是 | |
GBK | 2 | B37E | 是 | |||
GB2312 | 1 | 3F | 否 | |||
GB18030 | zh_CN.GBK | UTF-8 | 3 | E7A19A | 是 | |
GBK | 2 | B37E | 是 | |||
GB2312 | 1 | 3F | 否 | |||
GB18030 | zh_CN.GB2312 | UTF-8 | 3 | EFBFBD | 否 | |
GBK | 1 | 3F | 否 | |||
GB2312 | 1 | 3F | 否 | |||
GB2312 | en_US.UTF-8 | UTF-8 | 4 | EFBFBD7E | 否 | |
GBK | 2 | 3F7E | 否 | |||
GB2312 | 2 | 3F7E | 否 | |||
GB2312 | zh_CN.GB18030 | UTF-8 | 3 | E7A19A | 是 | |
GBK | 2 | B37E | 是 | |||
GB2312 | 1 | 3F | 否 | |||
GB2312 | zh_CN.GBK | UTF-8 | 3 | E7A19A | 是 | |
GBK | 2 | B37E | 是 | |||
GB2312 | 1 | 3F | 否 | |||
GB2312 | zh_CN.GB2312 | UTF-8 | 3 | EFBFBD | 否 | |
GBK | 1 | 3F | 否 | |||
GB2312 | 1 | 3F | 否 |
正确取值 (硚口的"硚")
UTF-8:E7A19A
GBK(GB18030/GB2312):B37E
通过测试结果得到的推论:
推论1 > 当文件编码是UTF-8时,只有系统环境也是en_US.UTF-8才能得到正确的字节码。
推论2 > 当文件编码是GB18030或GB2312中文字符编码时,系统环境是zh_CN.GB18030或zh_CN.GBK,则可获得正确编码信息。
推论3 > 如果一个汉字字符不再GB2312字符集中,则用GB2312编码是不可能取得正确的字节数组的。
我的建议:
当你的编码环境与打包环境不同(即编辑与编译环境),比如你本地编码然后上传到服务器用ant编译打包,那么需要确保文件编码与系统编码的一致性。
如果你在使用socket,请确保服务端和客户端编解码的一致性。发送端传出的是utf8,那么接受端也要用utf8来接受。转码的过程可以在发送前或者接受后。
一个例子,比如客户端环境是utf8,而协议设定是GB2312,那么客户端发送字节时需要用text.getBytes("GB2312")来取得字节数组,结果如果包含类似“硚”这样的不在GB2312的汉字就会产生乱码,乱码的产生是在客户端发送时就已经产生了,服务端不管用什么编码方式取得字节都会是乱码。所以基本上请不要使用GB2312,教科书上过时了的东西,很久了。
用UTF-8或GBK就可以了。查找乱码处就使用转码前后打印字节长度和字节哈希值的方法(我讨厌看到 3F,当没有与之对应的编码位置时就会产生这个;如果是eclispe断点看字节的十进制值的话,应该是该死的63吧,对就是63,3×16+15=63,就是它,有它必乱码 )。
附录:
前面的测试方法其实可以写一个shell脚本来做,修改文本的编码需要用到enca这个包可以去
ftp://fr2.rpmfind.net/linux/dag/redhat/el5/en/i386/dag/RPMS/enca-1.10-1.el5.rf.i386.rpm 下载安装。
但不晓得是不是版本原因,测试enca命令的时候,发现不管是修改文本编码为GB18030还是GBK都是改成了GB2312格式。
shell例子如下(文件编码仅UTF-8、GB2312),测试结果与上面表格一致。注意如果你是winows下编辑上传到linux的,要么在editplus中设定 文档--文件格式--UNIX ,要么上传到linux执行 dos2unix filename.sh 处理一下换行符。
#!/bin/sh
#测试一 文件编码:UTF-8 ---------------------------------------------------------------------------------
#文件编码:UTF-8 系统环境:en_US.UTF-8
enca -L zh_CN -x UTF-8 TestFileEncoding.java
fileEncoding=`enca -L zh_CN TestFileEncoding.java`
echo file encoding is `echo $fileEncoding | awk -F"; " '{print $2}'| awk -F" " '{print $1}'`
export LANG=en_US.UTF-8
echo system lang is $LANG
javac TestFileEncoding.java
java TestFileEncoding
echo
#文件编码:UTF-8 系统环境:zh_CN.GB18030
enca -L zh_CN -x UTF-8 TestFileEncoding.java
fileEncoding=`enca -L zh_CN TestFileEncoding.java`
echo file encoding is `echo $fileEncoding | awk -F"; " '{print $2}'| awk -F" " '{print $1}'`
export LANG=zh_CN.GB18030
echo system lang is $LANG
javac TestFileEncoding.java
java TestFileEncoding
echo
#文件编码:UTF-8 系统环境:zh_CN.GBK
enca -L zh_CN -x UTF-8 TestFileEncoding.java
fileEncoding=`enca -L zh_CN TestFileEncoding.java`
echo file encoding is `echo $fileEncoding | awk -F"; " '{print $2}'| awk -F" " '{print $1}'`
export LANG=zh_CN.GBK
echo system lang is $LANG
javac TestFileEncoding.java
java TestFileEncoding
echo
#文件编码:UTF-8 系统环境:zh_CN.GB2312
enca -L zh_CN -x UTF-8 TestFileEncoding.java
fileEncoding=`enca -L zh_CN TestFileEncoding.java`
echo file encoding is `echo $fileEncoding | awk -F"; " '{print $2}'| awk -F" " '{print $1}'`
export LANG=zh_CN.GB2312
echo system lang is $LANG
javac TestFileEncoding.java
java TestFileEncoding
echo
#测试二 文件编码:GB2312 ---------------------------------------------------------------------------------
#文件编码:GB2312 系统环境:en_US.UTF-8
enca -L zh_CN -x GB2312 TestFileEncoding.java
fileEncoding=`enca -L zh_CN TestFileEncoding.java`
echo file encoding is `echo $fileEncoding | awk -F"; " '{print $2}'| awk -F" " '{print $1}'`
export LANG=en_US.UTF-8
echo system lang is $LANG
javac TestFileEncoding.java
java TestFileEncoding
echo
#文件编码:GB2312 系统环境:zh_CN.GB18030
enca -L zh_CN -x GB2312 TestFileEncoding.java
fileEncoding=`enca -L zh_CN TestFileEncoding.java`
echo file encoding is `echo $fileEncoding | awk -F"; " '{print $2}'| awk -F" " '{print $1}'`
export LANG=zh_CN.GB18030
echo system lang is $LANG
javac TestFileEncoding.java
java TestFileEncoding
echo
#文件编码:GB2312 系统环境:zh_CN.GBK
enca -L zh_CN -x GB2312 TestFileEncoding.java
fileEncoding=`enca -L zh_CN TestFileEncoding.java`
echo file encoding is `echo $fileEncoding | awk -F"; " '{print $2}'| awk -F" " '{print $1}'`
export LANG=zh_CN.GBK
echo system lang is $LANG
javac TestFileEncoding.java
java TestFileEncoding
echo
#文件编码:GB2312 系统环境:zh_CN.GB2312
enca -L zh_CN -x GB2312 TestFileEncoding.java
fileEncoding=`enca -L zh_CN TestFileEncoding.java`
echo file encoding is `echo $fileEncoding | awk -F"; " '{print $2}'| awk -F" " '{print $1}'`
export LANG=zh_CN.GB2312
echo system lang is $LANG
javac TestFileEncoding.java
java TestFileEncoding
echo
你可以试试将测试字符“硚”换成“中文”。