中文化和国际化问题权威解析之四:Java中文化和国际化攻略

一般攻略

既然在Java内部是直接使用Unicode表示一切字符的,表达中文自然不成问题。因此所谓的中文问题并不是由Java本身引起的。而是因为对JavaUnicode理解不透或应用不当引起的。下面列出的原则,是解决一切中文问题的总纲:

  1. Java内部,正确使用Unicode标准。对于中文来说,每个汉字使用一个char表示。
  2. 在所有的输入输出环节,指明正确的编码方式,进行正确的字符到字节,或字节到字符的转换。
  3. 如果输入源或输出目标直接支持,尽可能直接使用Unicode进行输入输出。例如,Oracle数据库直接支持UTF-8的文本数据。使用UTF-8操作Oracle,可自动兼容所有的语言文字;反之,使用ISO-8859-1或者ASCII去操作Oracle,只能兼容欧美单字节的文字。
  4. 不要依赖平台默认的字符编码方式。例如,中文Windows下,默认编码为GBK,英文Linux下,默认编码为ISO-8859-1。依赖平台默认值意味着同样的程序在不同的平台上可能产生不同的结果。

 

遗留代码攻略

对于第三方的代码,或是以前遗留的代码,如果没有留有指定字符编码的接口,那么这些代码很可能使用默认的系统编码,或是使用固定的字符编码。这样很容易造成上述的各种中文乱码的问题。对于这些代码,我们可以做一个适配器,将它们返回的字符串转换成适当的Unicode内码。

例如,我们的数据库错误地使用了ASCII编码存储文本,也就是说从数据库返回的中文字,实际上被成了两个欧洲字符。但是数据库中已经保存了大量数据,想要把它改成正确的UTF-8存储格式并不容易。作为权宜之计,我们可以在数据访问层做一个适配器,将欧洲字符重新组合,变成真正的Unicode中文。

  1. public class DBAdapter {
  2.     private DBObject obj;
  3.     // 重新组合字节,转变成真正的unicode字符串
  4.     public String getString() {
  5.         String str = obj.getString();
  6.         try {
  7.             return new String(str.getBytes("8859_1"), "GBK");
  8.         } catch (UnsupportedEncodingException e) {
  9.             return str; // 不会执行到此处
  10.         }
  11.     }
  12.     // 将unicode字符串中的中文拆成两个欧洲字符,以便数据库保存
  13.     public void setString(String str) {
  14.         try {
  15.             str = new String(str.getBytes("GBK"), "8859_1");
  16.         } catch (UnsupportedEncodingException e) {
  17.         }
  18.         obj.setString(str);
  19.     }
  20. }

 

中文化和国际化问题权威解析之四:Java中文化和国际化攻略_第1张图片

 

WEB应用攻略

除了Unicode以外,无论何种本地字符集,都不能代表所有字符。这将导致一些问题:

  1. 难以在一屏幕显示多种语言的文字。
  2. 解码用户表单困难。

我们知道浏览器是根据当前页面的content type中指定的字符编码来发送用户的表单输入的。假设当前页面的content typetext/html; charset=GBK,则当用户按下submit按钮提交表单时,浏览器自动将用户输入的字符以GBK方式编码并发回到服务器端。假设页面的content typetext/html; charset=BIG5,则用户的输入将以BIG5繁体中文的编码发送。但是,如果用户输入的字符超过了这个编码字符集的范围,会怎样呢?我们可以写一个简单的JSP做试验:

  1. <%@page contentType="text/html; charset=BIG5"%>
  2. <html>
  3.     <head>
  4.         <title>Form test</title>
  5.         <meta http-equiv="Content-Type" content="text/html; charset=BIG5"/>
  6.     </head>
  7.     <body>
  8.         <p>Character Encoding: <%=request.getCharacterEncoding()%></p>
  9.         <%
  10.             String mytext = request.getParameter("mytext");
  11.             if (mytext != null) {
  12.                 out.println("<p>Value of parameter <code>mytext<code>:");
  13.                 out.println("<table border='1'>");
  14.                 out.println("  <tr><th>Display</th><th>Unicode</th><th>BIG5 code</tr>");
  15.                 for (int i = 0; i < mytext.length(); i++) {
  16.                     char   ch        = mytext.charAt(i);
  17.                     byte[] big5bytes = Character.toString(ch).getBytes("BIG5");
  18.                     int    big5code  = 0;
  19.                     out.print("  <tr><td>" + ch + "</td><td>");
  20.                     out.print(Integer.toHexString(0xFFFF & mytext.charAt(i)) + "</td><td>");
  21.                     for (int j = big5bytes.length - 1; j >= 0; j--) {
  22.                         big5code = (big5code << 8) + (0xFF & big5bytes[j]);
  23.                     }
  24.                     out.print(Integer.toHexString(big5code) + "</td></tr>");
  25.                 }
  26.                 out.println("</table></p>");
  27.             } else {
  28.                 mytext = "";
  29.             }
  30.         %>
  31.         <form action="<%=request.getRequestURI()%>" method="GET">
  32.             <textarea name="mytext"><%=mytext%></textarea><br>
  33.             <input type="submit"/>
  34.         </form>
  35.     </body>
  36. </html>

中文化和国际化问题权威解析之四:Java中文化和国际化攻略_第2张图片

上述页面是用BIG5显示的。在文本框中打入简体中文字我爱,然后submit。在结果页面中,我们可以看到被转换成了BIG5编码DAA7,而简体中文BIG5中没有对应的编码,因此被浏览器直接以&#29233;的形式返回。其中29233是简体中文的十进制Unicode码。

可见浏览器会把超出当前字符集的字符,以实体编码的形式(如&#29233;),直接返回给服务器端。Java servlet并不会自动处理这样的输入值,这给进一步处理字符串造成了困难。

为什么不直接使用UTF-8作为WEB页面的编码呢?这样不仅可以让全世界的文字同时显示在同一屏幕上(只要安装了相应的字体),也大大简化了解码用户表单的工作(不需要处理&#29233;这样的实体编码)。但使用UTF-8也会带来一些微小的不便:

  1. 一个中文需要用三个字节表示,稍微增加了网页的大小。但多数网页中的中文字的数量是非常用限的,因而字节数的增加也是非常有限的。例如Alibaba中文站的首页面,改成UTF-8以后,比GBK编码的页面仅仅增加了1413个字节。
  2. 使用不支持UTF-8的编辑器查看页面将看到乱码。但我们可以使用支持UTF-8的文本编辑器来查看页面的HTML源代码。此外,使用UTF-8编码WEB页面并非意味着用来生成WEB页面的模板也必须使用UTF-8。仍然可以使用GBK来书写WEB页面模板。

根据上面的讨论,我们得到如下最佳攻略:

1.            使用UTF-8作为WEB页面的编码。使用如下语句设置content type

  1. response.setContentType("text/html; charset=UTF-8");

并且在WEB页的HTML中设置标记:

  1. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>

对于Turbine,可在其配置文件中设置:

  1. locale.default.charset=UTF-8

Turbine将根据上述设置,自动为你设置content type

2.            仍然可以使用GBK来书写页面模板。对于以Velocity为模板系统的Turbine,需要在Turbine的配置文件中设置:

  1. services.VelocityService.input.encoding=GBK

模板的内容将以GBK的方式转换成Unicode,最后以UTF-8的方式输出到用户浏览器。

3.            使用UTF-8解码用户输入的表单。有几种方式可以达到这个目的:

    • 设置服务器特定的配置文件。对于Resin Server,需要在其配置文件resin.conf中设置:<web-app character-encoding="UTF-8"/>;对于Weblogic Server,需要设置WEB-INF/weblogic.xml配置文件,具体方法参见BEA文档

o  创建一个javax.servlet.Filter,在servlet被调用前,调用request.setCharacterEncoding方法:

  1. import java.io.IOException;
  2. import javax.servlet.Filter;
  3. import javax.servlet.FilterChain;
  4. import javax.servlet.FilterConfig;
  5. import javax.servlet.ServletException;
  6. import javax.servlet.ServletRequest;
  7. import javax.servlet.ServletResponse;
  8. public class SetCharacterEncodingFilter implements Filter {
  9.     public void init(FilterConfig config) throws ServletException {
  10.     }
  11.     public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
  12.             throws IOException, ServletException {
  13.         request.setCharacterEncoding("UTF-8");
  14.         chain.doFilter(request, response);
  15.     }
  16.     public void destroy() {
  17.     }
  18. }

o  servlet代码中调用:

  1. request.setCharacterEncoding("UTF-8");
Java Mail
攻略

WEB应用完全类似,使用Java Mail API同样需要设置正确的content type和字符编码。

  1. import javax.mail.internet.ContentType;
  2. import javax.mail.internet.MimeUtility;
  3. import javax.mail.Part;
  4. ...
  5. Part part;
  6. ContentType contentType;
  7. ...
  8. contentType = new ContentType("text/plain");  // 或"text/html"
  9. contentType.setParameter("charset", MimeUtility.mimeCharset("UTF-8"));
  10. part.setContent("... text or HTML content ...", contentType.toString());

值得注意的是,RFC 822标准规定,e-mailheader不能包含非ASCII编码。也就是说,e-mail的主题(subject)不能包含中文。那我们怎样在e-mailsubject中发送中文呢?还好,另一个标准RFC 2047定义了如何将非ASCIIheader转换成ASCII的规则。我们不需要了解这个规则的细节,只要调用javax.mail.internet.MimeUtility就可以完成转换了:

  1. import javax.mail.internet.MimeMessage;
  2. import javax.mail.internet.MimeUtility;
  3. ...
  4. MimeMessage message;
  5. ...
  6. message.setSubject(MimeUtility.encodeText("... e-mail的主题 ...",
  7.                                           MimeUtility.mimeCharset("UTF-8"), null));

大结论

完成同一个目标,往往有许多种途径,总有一些途径是比较好的,也有一些是不太好的。那些不太好的途径,虽然也能完成任务,却会导致很多潜在的问题。这些问题可能要在一定的条件下才能表现出来。例如,我们公司的Java程序以及Oracle数据库,从一开始就没有正确使用Unicode来存取文本,结果导致了一系列的问题。而要修正这样的问题,代价是比较大的。

怎样的途径是好的呢?一般来说,符合业界标准的,或是市场标准的实现途径,是我们首选的途径。所以我们设计任何程序之前,一定要充分了解相应的标准,不能不求甚解,完成任务了事。

Unicode标准及相关技术,不仅是解决中文问题的关键,而且是以统一的方法解决国际所有语言文字的问题的最好途径。使用好这些标准,必将为我们公司的产品的进一步向国际化发展,打好坚实的基础。

你可能感兴趣的:(java,oracle,数据库,Web,servlet,浏览器)