在基于 Java 语言的编程中我们经常碰到汉字的处理及显示的问题一大堆看不懂的乱码肯定不是我们愿意看到的显示效果怎样才能够让那些汉字正确显示呢?Java语言默认的编码方式是UNICODE而我们中国人通常使用的文件和数据库都是基于GB或者BIG等方式编码的怎样才能够恰当地选择汉字编码方式并正确地处理汉字的编码呢?本文将从汉字编码的常识入手结合Java编程实例分析以上两个问题并提出解决它们的方案
现在 Java 编程语言已经广泛应用于互联网世界早在 Sun 公司开发 Java 语言的时候就已经考虑到对非英文字符的支持了Sun 公司公布的 Java 运行环境(JRE)本身就分英文版和国际版但只有国际版才支持非英文字符不过在 Java 编程语言的应用中对中文字符的支持并非如同 Java Soft 的标准规范中所宣称的那样完美因为中文字符集不只一个而且不同的操作系统对中文字符的支持也不尽相同所以会有许多和汉字编码处理有关的问题在我们进行应用开发中困扰着我们有很多关于这些问题的解答但都比较琐碎并不能够满足大家迫切解决问题的愿望关于 Java 中文问题的系统研究并不多本文从汉字编码常识出发分析 Java 中文问题希望对大家解决这个问题有所帮助
汉字编码的常识
我们知道英文字符一般是以一个字节来表示的最常用的编码方法是 ASCII 但一个字节最多只能区分个字符而汉字成千上万所以现在都以双字节来表示汉字为了能够与英文字符分开每个字节的最高位一定为这样双字节最多可以表示K格字符我们经常碰到的编码方式有 GBBIGUNICODE 等关于具体编码方式的详细资料有兴趣的读者可以查阅相关资料我肤浅谈一下和我们关系密切的 GB 和 UNICODEGB 码中华人民共和国国家标准汉字信息交换用编码是一个由中华人民共和国国家标准总局发布的关于简化汉字的编码通行于中国大陆地区及新加坡简称国标码两个字节中第一个字节(高字节)的值为区号值加(H)第二个字节(低字节)的值为位号值加(H)用这两个值来表示一个汉字的编码UNICODE 码是微软提出的解决多国字符问题的多字节等长编码它对英文字符采取前面加“”字节的策略实现等长兼容如 “A” 的 ASCII 码为xUNICODE 就为xx利用特殊的工具各种编码之间可以互相转换
Java 中文问题的初步认识
我们基于 Java 编程语言进行应用开发时不可避免地要处理中文Java 编程语言默认的编码方式是 UNICODE而我们通常使用的数据库及文件都是基于 GB 编码的我们经常碰到这样的情况浏览基于 JSP 技术的网站看到的是乱码文件打开后看到的也是乱码被 Java 修改过的数据库的内容在别的场合应用时无法继续正确地提供信息
String sEnglish = “apple”;
String sChinese = “苹果”;
String s = “苹果 apple ”;
sEnglish 的长度是sChinese的长度是而 s 默认的长度是对于 sEnglish来说 Java 中的各个类都支持得非常好肯定能够正确显示但对于 sChinese 和 s 来说虽然 Java Soft 声明 Java 的基本类已经考虑到对多国字符的支持(默认 UNICODE 编码)但是如果操作系统的默认编码不是 UNICODE 而是国标码等从 Java 源代码到得到正确的结果要经过 “Java 源代码> Java 字节码> ;虚拟机>操作系统>显示设备”的过程在上述过程中的每一步骤我们都必须正确地处理汉字的编码才能够使最终的显示结果正确
“ Java 源代码> Java 字节码”标准的 Java 编译器 javac 使用的字符集是系统默认的字符集比如在中文 Windows 操作系统上就是 GBK 而在 Linux 操作系统上就是ISO所以大家会发现在 Linux 操作系统上编译的类中源文件中的中文字符都出了问题解决的办法就是在编译的时候添加 encoding 参数这样才能够与平台无关用法是
javac ?Cencoding GBK
“ Java 字节码>虚拟机>操作系统” Java 运行环境 (JRE) 分英文版和国际版但只有国际版才支持非英文字符 Java 开发工具包 (JDK) 肯定支持多国字符但并非所有的计算机用户都安装了 JDK 很多操作系统及应用软件为了能够更好的支持 Java 都内嵌了 JRE 的国际版本为自己支持多国字符提供了方便
“操作系统>显示设备”对于汉字来说操作系统必须支持并能够显示它英文操作系统如果不搭配特殊的应用软件的话是肯定不能够显示中文的
还有一个问题就是在 Java 编程过程中对中文字符进行正确的编码转换例如向网页输出中文字符串的时候不论你是用
out
println(string);
还是用<%=string%>都必须作 UNICODE 到 GBK 的转换或者手动或者自动在 JSP 中可以定义输出字符集从而实现内码的自动转换用法是
<%@page contentType=”text/html;charset=gb
” %>
但是在一些 JSP 版本中并没有提供对输出字符集的支持(例如 JSP )这就需要手动编码输出了方法非常多最常用的方法是
String s
= request
getParameter(“keyword”);
String s
= new String(s
getBytes(“ISO
”)
”GBK”);
getBytes 方法用于将中文字符以“ISO”编码方式转化成字节数组而“GBK” 是目标编码方式我们从以ISO方式编码的数据库中读出中文字符串 s 经过上述转换过程在支持 GBK 字符集的操作系统和应用软件中就能够正确显示中文字符串 s
Java 中文问题的表层分析及处理
背景 开发环境 JDK Vcafe JPadPro 服务器端 NT IIS Sybase System Jconnect(JDBC) 客户端 IE Pwin
CLASS 文件存放在服务器端由客户端的浏览器运行 APPLET APPLET 只起调入 FRAME 类等主程序的作用界面包括 Textfield TextAreaListChoice 等
I用 JDBC 执行 SELECT 语句从服务器端读取数据(中文)后将数据用 APPEND 方法加到 TextArea(TA) 不能正确显示但加到 List 中时大部分汉字却可正确显示
将数据按“ISO” 编码方式转化为字节数组再按系统缺省编码方式 (Default Character Encoding) 转化为 STRING 即可在 TA 和 List 中正确显示
程序段如下
dbstr
= results
getString(
);
//After reading the result from DB server
converting it to string
dbbyte
= dbstr
getBytes(“iso
”);
dbstr
= new String(dbbyte
);
在转换字符串时不采用系统默认编码方式而直接采用“ GBK” 或者 “GB” 在 A 和 B 两种情况下从数据库取数据都没有问题
II处理方式与“取中文”相逆先将 SQL 语句按系统缺省编码方式转化为字节数组再按“ISO”编码方式转化为 STRING 最后送去执行则中文信息可正确写入数据库
程序段如下
sqlstmt = tf_input
getText();
//Before sending statement to DB server
converting it to sql statement
dbbyte
= sqlstmt
getBytes();
sqlstmt = newString(dbbyte
”iso
”);
_stmt = _con
createStatement();
_stmt
executeUpdate(sqlstmt);
……
问题如果客户机上存在 CLASSPATH 指向 JDK 的 CLASSESZIP 时(称为 A 情况)上述程序代码可正确执行但是如果客户机只有浏览器而没有 JDK 和 CLASSPATH 时(称为 B 情况)则汉字无法正确转换
我们的分析
经过测试在 A 情况下程序运行时系统的缺省编码方式为 GBK 或者 GB 在 B 情况下程序启动时浏览器的 JAVA 控制台中出现如下错误信息
Cant find resource for sunawtwindowsawtLocalization_zh_CN
然后系统的缺省编码方式为“”
如果在转换字符串时不采用系统缺省编码方式而是直接采用 “GBK” 或“GB”则在 A 情况下程序仍然可正常运行在 B 情况下系统出现错误
UnsupportedEncodingException
在客户机上把 JDK 的 CLASSESZIP 解压后放在另一个目录中 CLASSPATH 只包含该目录然后一边逐步删除该目录中的 CLASS 文件另一边运行测试程序最后发现在一千多个 CLASS 文件中只有一个是必不可少的该文件是
sunioCharToByteDoubleByteclass
将该文件拷到服务器端和其它的类放在一起并在程序的开头 IMPORT 它在 B 情况下程序仍然无法正常运行
在 A 情况下如果在 CLASSPTH 中去掉 sunioCharToByteDoubleByteclass 则程序运行时测得默认编码方式为“”否则为 “GBK” 或 “GB”
如果 JDK 的版本为以上的话在 B 情况下遇到的问题得到了很好的解决测试的步骤同上有兴趣的读者可以尝试一下
Java 中文问题的根源分析及解决
在简体中文 MS Windows + JDK 下可以用 SystemgetProperties() 得到 Java 运行环境的一些基本属性类 PoorChinese 可以帮助我们得到这些属性
类 PoorChinese 的源代码
public class PoorChinese {}
执行 java PoorChinese 后我们会得到:
系统变量 fileencoding 的值为 GBK userlanguage 的值为 zh userregion 的值为 CN 这些系统变量的值决定了系统默认的编码方式是 GBK
在上述系统中下面的代码将 GB 文件转换成 Big 文件它们能够帮助我们理解 Java 中汉字编码的转化:
import java
io
*;
import java
util
*;
public class gb
big
{
static int iCharNum=
;
public static void main(String[] args) {
System
out
println("Input GB
file
output Big
file
");
if (args
length!=
)
{
System
err
println("Usage: jview gb
big
gbfile big
file");
System
exit(
);
String inputString = readInput(args[
]);
writeOutput(inputString
args[
]);
System
out
println("Number of Characters in file: "+iCharNum+"
");
}
static void writeOutput(String str
String strOutFile)
{
try
{
FileOutputStream fos = new FileOutputStream(strOutFile);
Writer out = new OutputStreamWriter(fos
"Big
");
out
write(str);
out
close();
}
catch (IOException e)
{
e
printStackTrace();
e
printStackTrace();
}
}
static String readInput(String strInFile)
{
StringBuffer buffer = new StringBuffer();
try
{
FileInputStream fis = new FileInputStream(strInFile);
InputStreamReader isr = new InputStreamReader(fis
"GB
");
Reader in = new BufferedReader(isr);
int ch;
while ((ch = in
read()) >
)
{
iCharNum +=
;buffer
append((char)ch);
}
in
close();
return buffer
toString();
}
catch (IOException e)
{
e
printStackTrace();
return null;
}
}
}
编码转化的过程如下
GB>Unicode>Big
执行 java gbbig gbtxt bigtxt 如果 gbtxt 的内容是“今天星期三”则得到的文件 bigtxt 中的字符能够正确显示而如果 gbtxt 的内容是“情人节快乐”则得到的文件 bigtxt 中对应于“节”和“乐”的字符都是符号“?”(xF)可见 sunioByteToCharGB 和 sunioCharToByteBig 这两个基本类并没有编好
正如上例一样 Java 的基本类也可能存在问题由于国际化的工作并不是在国内完成的所以在这些基本类发布之前没有经过严格的测试所以对中文字符的支持并不像 Java Soft 所声称的那样完美前不久我的一位技术上的朋友发信给我说他终于找到了 Java Servlet 中文问题的根源两周以来他一直为 Java Servlet 的中文问题所困扰因为每面对一个含有中文字符的字符串都必须进行强制转换才能够得到正确的结果(这好象是大家公认的唯一的解决办法)
后来他确实不想如此继续安分下去了因为这样的事情确实不应该是高级程序员所要做的工作他就找出 Servlet 解码的源代码进行分析因为他怀疑问题就出在解码这部分经过四个小时的奋斗他终于找到了问题的根源所在原来他的怀疑是正确的 Servlet 的解码部分完全没有考虑双字节直接把 %XX 当作一个字符(原来 Java Soft 也会犯这幺低级的错误!)
如果你对这个问题有兴趣或者遇到了同样的烦恼的话你可以按照他的步骤 对Servletjar 进行修改
找到源代码 HttpUtils 中的 static private String parseName 在返回前将 sb(StringBuffer) 复制成 byte bs[] 然后 return new String(bs”GB”)作上述修改后就需要自己解码了
HashTable form=HttpUtils
parseQueryString(request
getQueryString())或者
form=HttpUtils
parsePostData(……)
千万别忘了编译后放到 Servletjar 里面
关于 Java 中文问题的总结
Java 编程语言成长于网络世界这就要求 Java 对多国字符有很好的支持 Java 编程语言适应了计算的网络化的需求为它能够在网络世界迅速成长奠定了坚实的基础 Java 的缔造者 (Java Soft) 已经考虑到 Java 编程语言对多国字符的支持只是现在的解决方案有很多缺陷在里面需要我们付诸一些补偿性的措施而世界标准化组织也在努力把人类所有的文字统一在一种编码之中其中一种方案是 ISO 它用四个字节来表示一个字符当然在这种方案未被采用之前还是希望 Java Soft 能够严格地测试它的产品为用户带来更多的方便
附一个用于从数据库和网络中取出 中文乱码的处理函数入参是有问题的字符串出参是问题已经解决了的字符串