Java中文乱码之一 文件编码与系统环境

来源:互联网 发布:怎么查手机的网络制式 编辑:程序博客网 时间:2024/06/07 01:22

转自: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-8E7A19A

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

你可以试试将测试字符“硚”换成“中文”。
0 0