一般攻略
既然在Java内部是直接使用Unicode表示一切字符的,表达中文自然不成问题。因此所谓的中文问题并不是由Java本身引起的。而是因为对Java和Unicode理解不透或应用不当引起的。下面列出的原则,是解决一切中文问题的总纲:
- 在Java内部,正确使用Unicode标准。对于中文来说,每个汉字使用一个char表示。
- 在所有的输入输出环节,指明正确的编码方式,进行正确的字符到字节,或字节到字符的转换。
- 如果输入源或输出目标直接支持,尽可能直接使用Unicode进行输入输出。例如,Oracle数据库直接支持UTF-8的文本数据。使用UTF-8操作Oracle,可自动兼容所有的语言文字;反之,使用ISO-8859-1或者ASCII去操作Oracle,只能兼容欧美单字节的文字。
- 不要依赖平台默认的字符编码方式。例如,中文Windows下,默认编码为GBK,英文Linux下,默认编码为ISO-8859-1。依赖平台默认值意味着同样的程序在不同的平台上可能产生不同的结果。
遗留代码攻略
对于第三方的代码,或是以前遗留的代码,如果没有留有指定字符编码的接口,那么这些代码很可能使用默认的系统编码,或是使用固定的字符编码。这样很容易造成上述的各种中文乱码的问题。对于这些代码,我们可以做一个适配器,将它们返回的字符串转换成适当的Unicode内码。
例如,我们的数据库错误地使用了ASCII编码存储文本,也就是说从数据库返回的中文字,实际上被“拆”成了两个欧洲字符。但是数据库中已经保存了大量数据,想要把它改成正确的UTF-8存储格式并不容易。作为权宜之计,我们可以在数据访问层做一个适配器,将欧洲字符重新组合,变成真正的Unicode中文。
- public class DBAdapter {
- private DBObject obj;
-
- public String getString() {
- String str = obj.getString();
- try {
- return new String(str.getBytes("8859_1"), "GBK");
- } catch (UnsupportedEncodingException e) {
- return str;
- }
- }
-
- public void setString(String str) {
- try {
- str = new String(str.getBytes("GBK"), "8859_1");
- } catch (UnsupportedEncodingException e) {
- }
- obj.setString(str);
- }
- }
WEB
应用攻略
除了Unicode以外,无论何种本地字符集,都不能代表所有字符。这将导致一些问题:
- 难以在一屏幕显示多种语言的文字。
- 解码用户表单困难。
我们知道浏览器是根据当前页面的content type中指定的字符编码来发送用户的表单输入的。假设当前页面的content type为text/html; charset=GBK,则当用户按下submit按钮提交表单时,浏览器自动将用户输入的字符以GBK方式编码并发回到服务器端。假设页面的content type为text/html; charset=BIG5,则用户的输入将以BIG5繁体中文的编码发送。但是,如果用户输入的字符超过了这个编码字符集的范围,会怎样呢?我们可以写一个简单的JSP做试验:
- <%@page contentType="text/html; charset=BIG5"%>
- <html>
- <head>
- <title>Form test</title>
- <meta http-equiv="Content-Type" content="text/html; charset=BIG5"/>
- </head>
- <body>
- <p>Character Encoding: <%=request.getCharacterEncoding()%></p>
- <%
- String mytext = request.getParameter("mytext");
- if (mytext != null) {
- out.println("<p>Value of parameter <code>mytext<code>:");
- out.println("<table border='1'>");
- out.println(" <tr><th>Display</th><th>Unicode</th><th>BIG5 code</tr>");
- for (int i = 0; i < mytext.length(); i++) {
- char ch = mytext.charAt(i);
- byte[] big5bytes = Character.toString(ch).getBytes("BIG5");
- int big5code = 0;
- out.print(" <tr><td>" + ch + "</td><td>");
- out.print(Integer.toHexString(0xFFFF & mytext.charAt(i)) + "</td><td>");
- for (int j = big5bytes.length - 1; j >= 0; j--) {
- big5code = (big5code << 8) + (0xFF & big5bytes[j]);
- }
- out.print(Integer.toHexString(big5code) + "</td></tr>");
- }
- out.println("</table></p>");
- } else {
- mytext = "";
- }
- %>
- <form action="<%=request.getRequestURI()%>" method="GET">
- <textarea name="mytext"><%=mytext%></textarea><br>
- <input type="submit"/>
- </form>
- </body>
- </html>
上述页面是用BIG5
显示的。在文本框中打入简体中文字“我爱”,然后submit。在结果页面中,我们可以看到“我”被转换成了BIG5编码DAA7,而简体中文“爱”在BIG5中没有对应的编码,因此被浏览器直接以爱的形式返回。其中29233是简体中文“爱”的十进制Unicode码。
可见浏览器会把超出当前字符集的字符,以实体编码的形式(如爱),直接返回给服务器端。Java servlet并不会自动处理这样的输入值,这给进一步处理字符串造成了困难。
为什么不直接使用UTF-8作为WEB页面的编码呢?这样不仅可以让全世界的文字同时显示在同一屏幕上(只要安装了相应的字体),也大大简化了解码用户表单的工作(不需要处理爱这样的实体编码)。但使用UTF-8也会带来一些微小的不便:
- 一个中文需要用三个字节表示,稍微增加了网页的大小。但多数网页中的中文字的数量是非常用限的,因而字节数的增加也是非常有限的。例如Alibaba中文站的首页面,改成UTF-8以后,比GBK编码的页面仅仅增加了1413个字节。
- 使用不支持UTF-8的编辑器查看页面将看到“乱码”。但我们可以使用支持UTF-8的文本编辑器来查看页面的HTML源代码。此外,使用UTF-8编码WEB页面并非意味着用来生成WEB页面的模板也必须使用UTF-8。仍然可以使用GBK来书写WEB页面模板。
根据上面的讨论,我们得到如下最佳攻略:
1. 使用UTF-8作为WEB页面的编码。使用如下语句设置content type:
- response.setContentType("text/html; charset=UTF-8");
并且在
WEB
页的
HTML
中设置标记:
- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
对于
Turbine,可在其配置文件中设置:
- locale.default.charset=UTF-8
Turbine
将根据上述设置,自动为你设置content type。
2. 仍然可以使用GBK来书写页面模板。对于以Velocity为模板系统的Turbine,需要在Turbine的配置文件中设置:
- services.VelocityService.input.encoding=GBK
模板的内容将以GBK
的方式转换成Unicode
,最后以UTF-8
的方式输出到用户浏览器。
3. 使用UTF-8解码用户输入的表单。有几种方式可以达到这个目的:
-
o 创建一个javax.servlet.Filter,在servlet被调用前,调用request.setCharacterEncoding方法:
- import java.io.IOException;
- import javax.servlet.Filter;
- import javax.servlet.FilterChain;
- import javax.servlet.FilterConfig;
- import javax.servlet.ServletException;
- import javax.servlet.ServletRequest;
- import javax.servlet.ServletResponse;
- public class SetCharacterEncodingFilter implements Filter {
- public void init(FilterConfig config) throws ServletException {
- }
- public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
- throws IOException, ServletException {
- request.setCharacterEncoding("UTF-8");
- chain.doFilter(request, response);
- }
- public void destroy() {
- }
- }
o
在
servlet
代码中调用:
- request.setCharacterEncoding("UTF-8");
Java Mail攻略
和WEB应用完全类似,使用Java Mail API同样需要设置正确的content type和字符编码。
- import javax.mail.internet.ContentType;
- import javax.mail.internet.MimeUtility;
- import javax.mail.Part;
- ...
- Part part;
- ContentType contentType;
- ...
- contentType = new ContentType("text/plain");
- contentType.setParameter("charset", MimeUtility.mimeCharset("UTF-8"));
- part.setContent("... text or HTML content ...", contentType.toString());
值得注意的是,RFC 822
标准规定,
e-mail
的
header
不能包含非ASCII
编码。也就是说,
e-mail
的主题(
subject
)不能包含中文。那我们怎样在
e-mail
的
subject
中发送中文呢?还好,另一个标准RFC 2047
定义了如何将非ASCII
的
header
转换成ASCII
的规则。我们不需要了解这个规则的细节,只要调用
javax.mail.internet.MimeUtility
就可以完成转换了: