首页 » 编写高质量代码:改善Java程序的151个建议 » 编写高质量代码:改善Java程序的151个建议全文在线阅读

《编写高质量代码:改善Java程序的151个建议》建议58:强烈建议使用UTF编码

关灯直达底部

Java的乱码问题由来已久,有点经验的开发人员肯定都遇到过乱码问题,有时是从Web上接收的乱码,有时是从数据库中读取的乱码,有时是在外部接口中接收到的乱码文件,这些都让我们困惑不已,甚至是痛苦不堪,看如下代码:


public static void main(Stringargs)throws Exception{

String str=/"汉字/";

//读取字节

byteb=str.getBytes(/"UTF-8/");

//重新生成一个新的字符串

System.out.println(new String(b));

}


Java文件是通过IDE工具默认创建的,编码格式是GBK,大家想想看上面的输出结果会是什么?可能是乱码吧?两个编码格式不相同。我们暂不公布结果,先解释一下Java中的编码规则。Java程序涉及的编码包括两部分:

(1)Java文件编码

如果我们使用记事本创建一个.java后缀的文件,则文件的编码格式就是操作系统默认的格式。如果是使用IDE工具创建的,如Eclipse,则依赖于IDE的设置,Eclipse默认是操作系统编码(Windows一般为GBK)。

(2)Class文件编码

通过javac命令生成的后缀名为.class的文件是UTF-8编码的UNICODE文件,这在任何操作系统上都是一样的,只要是class文件就会是UNICODE格式。需要说明的是,UTF是UNICODE的存储和传输格式,它是为了解决UNICODE的高位占用冗余空间而产生的,使用UTF编码就标志着字符集使用的是UNICODE。

再回到我们的例子上,getBytes方法会根据指定的字符集提取出字节数组(这里按照UNICODE格式来提取),然后程序又通过newString(bytebytes)重新生成一个字符串。来看看String这个构造函数:通过操作系统默认的字符集解码指定的byte数组,构造一个新的String。结果已经很清楚了,如果操作系统是UTF-8编码的话,输出就是正确的,如果不是,则会是乱码。由于这里使用的是默认编码GBK,那么输出的结果也就是乱码了。我们再详细分解一下运行步骤:

步骤1 创建Client.java文件。

该文件的默认编码GBK(如果使用Eclipse,则可以在属性查看到)。

步骤2 编写代码(如上)。

步骤3 保存,并使用javac编译。

注意我们没有使用/"javac-encoding GBK Client.java/"显式声明Java的编码格式,javac会自动按照操作系统的编码(GBK)读取Client.java文件,然后将其编译成.class文件。

步骤4 生成.class文件。

编译结束,生成.class文件,并保存到硬盘上。此时.class文件使用的是UTF-8格式编码的UNICODE字符集,可以通过javap命令阅读class文件。其中“汉字”变量也已经由GBK编码转变成UNICODE格式了。

步骤5 运行main方法,提取“汉字”的字节数组。

“汉字”原本是按照UTF-8格式保存的,要再提取出来当然没有任何问题了。

步骤6 重组字符串。

读取操作系统的编码格式(GBK),然后重新编码变量b的所有字节。问题就在这里产生了:因为UNICODE的存储格式是两个字节表示一个字符(注意这里是指UCS-2标准),虽然GBK也是2个字节表示一个字符,但两者之间没有影射关系,要想做转换只能读取映射表,不能实现自动转换——于是JVM就按照默认的编码格式(GBK)读取了UNICODE的两个字节。

步骤7 输出乱码,程序运行结束。

问题清楚了,解决方案也随之产生,方案有两个。

步骤8 修改代码。

明确指定编码即可,代码如下:


System.out.println(new String(b,/"UTF-8/"));


步骤9 修改操作系统的编码方式。

各个操作系统的修改方式不同,不再赘述。

我们可以把从字符串读取字节的过程看作是数据传输的需要(比如网络、存储),而重组字符串则是业务逻辑的需求,这样就可使乱码现场重现:通过JDBC读取的字节数组是GBK的,而业务逻辑编码时采用的是UTF-8,于是乱码产生了。对于此类问题,最好的解决办法就是使用统一的编码格式,要么都用GBK;要么都用UTF-8,各个组件、接口、逻辑层都用UTF-8,拒绝独树一帜的情况。

问题解释清楚了,我们再来看以下代码:


public class Client{

public static void main(Stringargs)throws Exception{

String str=/"汉字/";

//读取字节

byteb=str.getBytes(/"GB2312/");

//重新生成一个新的字符串

System.out.println(new String(b));

}

}


仅仅修改了读取字节的编码格式(修改成了GB2312格式的),结果会是怎样的呢?又或者将其修改成GB18030,结果又是怎样的呢?结果都是“汉字”,不是乱码。哈哈,这是因为GB2312是中文字符集的V1.0版,GBK是V2.0版本,GB18030是V3.0版,版本是向下兼容的,只是它们包含的汉字数量不同而已,注意,UNICODE可不在这个序列之内的。

注意 一个系统使用统一的编码。