周竞涛 (
[email protected])
王明微 (
[email protected])
西北工业大学CAD/CAM国家专业实验室
2002 年 12 月
尽管关于Java中文问题的讨论已经相当多了,但由于Java的相关技术标准繁多,面向Java的Web服务器、应用服务器以及JDBC数据库驱动等都没有官方的标准,所以Java应用在处理中文时所存在的问题不仅没有消失而且随着所选用的服务器、驱动程序以及运行环境等因素的不同而变化。那么我们如何从众多现象中找出问题所在,并进行分析和解决呢?与大部分的讨论不同,本文将主要从如何预测、发现和检查问题的角度给出建议,帮助开发人员找出可能引起问题的各种源头,从而更好地解决Java的中文问题。
引言
尽管对于Java中文处理问题的讨论已不乏其数,但由于Java技术涉及内容广(J2EE包含了十几种相关技术),技术供应商繁多,面向Java的Web服务器、应用服务器以及JDBC数据库驱动等都没有官方的标准,所以Java应用在处理中文过程中出了存在固有的问题外也存在随着选用的服务器,驱动程序的不同而带来的Java中文问题的多变性,增加了问题的复杂度。那么,我们如何在这么纷繁的现象中找到问题的症结呢?
Java中文问题的一般解决办法
事实上,Java的中文问题都是由于Java应用所采用的缺省编码格式与目标或者应用所要读入字符的编码格式不同而造成的(具体参见文献1)。对于如何解决Java的中文问题,通常有四种方法:
1) 选择JDK的中文本地化版本。尽管Java2 JDK的中文本地化版本(http://java.sun.com/products/jdk/1.2/chinesejdk.html)并不是一个官方的版本,Sun公司也没有承诺会对该本地化版本进行升级,但其仍不失为一个Java中文问题的解决方案。
2) 选择合适的编译参数。对于Java的国际版本来讲,我们也可以在编译Java应用的时候通过指定确定的编码机制来实现其编译结果对中文的支持。例如,对于需要支持繁体中文和简体中文应用可以通过javac -encoding big5 sourcefile.java 和javac -encoding gb2312 sourcefile.java来编译源程序。
3) 通过编程的方式实现字符编码的转换代码。通过编程的方式来解决Java的中文问题,已经成为了一种较为普遍的做法。下面就是一种最常见的字符编码转换函数,其将字符的编码格式转换为中文Windows系统的GBK编码形式。
public static String toChinese(String strvalue)
{
try{
if(strvalue==null)
return null;
else
{
strvalue = new String(strvalue.getBytes("ISO8859_1"), "GBK");
return strvalue;
}
}catch(Exception e){
return null;
}
}
4) 定义字符输出集。对于JSP应用,我们可以通过<%@ page contentType="text/html; charset=GBK" %>或<%@ page contentType="text/html; charset=GB2312" %>来定义JSP页面的字符输出集。当然,我们也可以通过HTML的标记
来定义字符的输出集。
存在的问题
根据方法实现的方式,我们可以将以上四种方法分为两类,一类是通过利用某些标准或者规则来实现的方法,上面的1)、2)、4)都属于此类;一类是通过针对性的编程来实现的方法,上面所提的方法3)就属于此类。
由于方法1),2),4)是具有规范性的一类方法,所以方法比较简单,解决方案也不具备较大的针对性,较为通用,例如我们可以采用方法2)的编译方式通过编译Java源文件来实现内码的预置,而无需考虑源码到底有哪些部分出现了Java的中文处理问题,诸如输出乱码等等。
但是,正由于这些方法不具备针对性,解决问题的方法过于统一,所以在某些情况下,它们并不能彻底地解决Java的中文问题。举一个非常常见的例子。在通常情况下,用户的Java应用往往需要与其它Java应用接口进行交互,例如通过某种版本的JDBC访问数据库。由于JDBC的驱动所支持的编码随着提供商乃至版本的不同而不同,所以如果在数据库的输入输出过程中出现中文不能正确处理问题时,我们需要在数据的输入和输出过程做两次正好相反的编码转换,这对于方法1),2),4)来说,往往是无法解决的。当然,对于方法2,我们也可以通过采用一些技巧使来满足上面的情况,一个最有效的办法就是尽量将Java应用的各个部分组件化。例如我们可以通过将数据库的读入和输出代码分解在不同的源文件上来实现分别编译,从而满足不同的字符编码要求。但是通常的程序设计都不太可能满足这种要求,因为这种程序的划分结果很可能是不合理的。例如,我们将数据库的读出和写入方法封装到一个类中是比较合适的一种设计,但如果将该类的这两个方法分别实现在两个文件里则变得非常不合理。因此对于1),2),4)方法来说,虽然实现比较简单,但却具有一些无法克服的缺点。这也是那些实现起来相对复杂的编程方法得以流行的原因。
相对于方法1),2),4)来说,方法3)具有更好的针对性和灵活性。程序可以根据不同的情况做出灵活的处理,在任何需要的地方进行字符的编码转换,但是该方法的特点也对软件的开发人员提出了更高要求--必须能够准确的捕捉到有可能发生中文处理问题的地方,并做出正确的判断和处理。
分析的原则
总的说来,所有解决Java中文处理的方法都不是很复杂。相反的是,由于Java技术特别是J2EE技术涉及的内容繁多,各种Web服务器、应用服务器以及JDBC数据库驱动等参差不齐,所以如何正确而及时的发现应用的中文处理问题则变得相对复杂的多。那么我们如何来发现这些问题呢?
通常,Java处理中文时所产生的问题都是由于用户的Java应用所采用的缺省编码格式与目标或者应用所要读入字符的编码格式不同而造成的,而引起这些不同的一个主要原因就是用户的Java应用与其它应用进行了编码格式不匹配的数据交换(包括直接或间接的数据输入、输出)。所以,为了及时发现问题,我们可以由这一点入手,根据以下的原则对应用进行分析:
注意字符变量情况。由于变量的字符编码形式较为隐蔽,多次变量间数值的改变和运算可能会引起字符集的改变;在变量与页面所提交数据的各种操作中,较容易发生不同编码格式字符进行运算的情况。
注意任何形式的字符读入与输出。之所以要提到任何形式,是因为Java应用大多数都是作为网络应用开发的,所以与其它语言的应用相比,Java应用需要面对网络世界各种各样的字符数据交换形式。例如各种表单的数据提交,URL形式的数据读入,经过加密运算的字符数据交换,网页控件选择结果的输入,控件内容的的显示(如List控件)等等。
小心使用第三方的组件和应用。由于第三方组件和应用的实现是非透明的,所以一般情况下,我们很难判断这些组件或驱动的缺省编码格式是什么,也无法对其进行控制。因此,在使用它们所提供的接口函数进行数据交换的时候要特别注意,如果确实出现中文无法正确处理情况,应首先检查我们自己的代码并调整相关代码以适应这些接口,因为这些组件或者应用基本上不会提供调整编码机制的接口。必要时,我们可能需要采用其它可替换的组件或者应用。
注意被请求对象所含有的数据输入与输出。这是非常隐蔽的一类情况,当我们的应用以对象的方式(例如序列化的对象)进行交互时,如果这个对象内部含有字符数据的处理过程,或者含有某些数据的输入、输出,甚至是抛出一段用中文注解的异常,都可能出现中文无法正确显示等问题。由于这些行为往往被封装在对象中,所以我们在编写程序时,很容易忽略这种可能情况。并且这种情况带有一定的不可预见性,例如我们可能不清楚这个对象会在什么时候抛出什么样的异常,所以这时我们就需要做一定的测试工作。
注意数据库的数据访问过程。Java通过JDBC与数据库建立连接。对于JDBC驱动程序来说,由于目前大部分的JDBC驱动程序并不是针对中文系统而设计的(中文数据大都采用ISO-8859-1编码方式),所以一般情况下在数据读写过程中往往都需要字符编码的转化。但是我们仍建议用户在使用这些JDBC驱动时,仔细阅读它的说明。如果确实无法弄清JDBC字符数据的编码到底是什么,我们的建议是做一些必要的测试。例如下面是一组在简体中文Win2000平台下,采用Weblogic 6.0所提供的JDBC驱动从MS SQL Server2000中正确读入中文字符的代码(例子中进行了字符运算):
...
Class.forName("weblogic.jdbc.mssqlserver4.Driver").newInstance();
conn = myDriver.connect("jdbc:weblogic:mssqlserver4", props);
conn.setCatalog("labmanager");
Statement st = conn.createStatement();
//execute a query
String testStr;
String testTempStr = new String() ;
testStr = new String(testTempStr.getBytes("ISO-8859-1"));//编码转化
DatabaseMetaData DBMetaData =conn.getMetaData();
ResultSet rs = DBMetaData.getTables(null, null,null,new String[]{"TABLE"} );
while (rs.next()){
for(int j=1; j<=rs.getMetaData().getColumnCount(); j++){
testStr = testStr +String(rs.getObject(j).toString().getBytes("ISO-8859-1"));
}
}
然而,需要注意的是,不同的JDBC驱动对相同的数据库的支持并不同,而同一类JDBC驱动对不同的数据库的支持也不相同,也就是说我们的字符转化代