首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

JSP/Servlet 中的汉字编码问题(2)

JSP/Servlet 中的汉字编码问题(2)

中文转码时'?'、乱码的由来两个方向转换都有可能得到错误的结果:
  • Unicode-->Byte, 如果目标代码集不存在对应的代码,则得到的结果是 0x3f.           如:            
    "\u00d6\u00ec\u00e9\u0046\u00bb\u00f9".getBytes("GBK") 的结果是"? ìé F? ù", Hex 值是 3fa8aca8a6463fa8b4.         
    仔细看一下上面的结果,你会发现 \u00ec 被转换为 0xa8ac,  \u00e9 被转换为 \xa8a6... 它的实际有效位变长了!这是因为 GB2312 符号区中的一些符号被映射到一些公共的符号编码,由于这些符号出现在 ISO-8859-1 或其它一些 SBCS 字符集中,故它们在 Unicode 中编码比较靠前,有一些其有效位只有 8 位,和汉字的编码重叠 ( 其实这种映射只是编码的映射,在显示时仔细不是一样的。Unicode 中的符号是单字节宽,汉字中的符号是双字节宽 ) . 在 Unicode\u00a0--\u00ff 之间这样的符号有 20 个。了解这个特征非常重要!由此就不难理解为什么 JAVA 编程中,汉字编码的错误结果中常常会出现一些乱码 ( 其实是符号字符 ), 而不全是'?'字符 , 就比如上面的例子。
  • Byte-->Unicode, 如果 Byte 标识的字符在源代码集不存在,则得到的结果是 0xfffd.           如:            
    Byte ba[] = {(byte)0x81,(byte)0x40,(byte)0xb0,(byte)0xa1}; new  String(ba,"gb2312");         
    结果是"? 啊", hex 值是"\ufffd\u554a". 0x8140 是 GBK 字符,按 GB2312 转换表没有对应的值,取 \ufffd.  ( 请注意:在显示该 uniCode 时,因为没有对应的本地字符,所以也适用上一种情况,显示为一个"?".)
实际编程中,JSP/Servlet 程序得到错误的汉字信息,往往是这两个过程的叠加,有时甚至是两个过程叠加后反复作用的结果 .
JSP/Servlet 汉字编码问题及在 WAS 中的解决办法4.1 常见的 encoding 问题的现象网上常出现的 JSP/Servlet encoding 问题一般都表现在 browser 或应用程序端,如 :
  • 浏览器中看到的 Jsp/Servlet 页面中的汉字怎么都成了 ’ ? ’ ?
  • 浏览器中看到的 Servlet 页面中的汉字怎么都成了乱码?
  • JAVA 应用程序界面中的汉字怎么都成了方块?
  • Jsp/Servlet 页面无法显示 GBK 汉字。
  • JSP 页面中内嵌在 <%...%>,<%=...%> 等 Tag 包含的 JAVA  code 中的中文成了乱码,但页面的其它汉字是对的。
  • Jsp/Servlet 不能接收 form 提交的汉字。
  • JSP/Servlet 数据库读写无法获得正确的内容。
隐藏在这些问题后面的是各种错误的字符转换和处理(除第 3 个外,是因为 Java font 设置错误引起的)。解决类似的字符 encoding 问题,需要了解 Jsp/Servlet 的运行过程,检查可能出现问题的各个点。
4.2 JSP/Servlet web 编程时的 encoding 问题运行于 Java 应用服务器的 JSP/Servlet 为 Browser 提供 HTML 内容,其过程如下图所示:
其中有字符编码转换的地方有 :
  • JSP 编译。Java 应用服务器将根据 JVM 的 file.encoding 值读取 JSP 源文件,编译生成 JAVA 源文件,再根据 file.encoding 值写回文件系统。如果当前系统语言支持 GBK,那么这时候不会出现 encoding 问题。如果是英文的系统,如 LANG 是 en_US 的 Linux, AIX 或 Solaris,则要将 JVM 的 file.encoding 值置成 GBK 。系统语言如果是 GB2312,则根据需要,确定要不要设置 file.encoding,将 file.encoding 设为 GBK 可以解决潜在的 GBK 字符乱码问题
  • Java 需要被编译为 .class 才能在 JVM 中执行,这个过程存在与 a. 同样的 file.encoding 问题。从这里开始 servlet 和 jsp 的运行就类似了,只不过 Servlet 的编译不是自动进行的。对于 JSP 程序 , 对产生的 JAVA 中间文件的编译是自动进行的 ( 在程序中直接调用 sun.tools.javac.Main 类 ). 因此如果在这一步出现问题的话 , 也要检查 encoding 和 OS 的语言环境,或者将内嵌在 JSP JAVA Code 中的静态汉字转为 Unicode, 要么静态文本输出不要放在 JAVA code 中。对于 Servlet, javac 编译时手工指定 -encoding 参数就可以了。
  • Servlet 需要将 HTML 页面内容转换为 browser 可接受的 encoding 内容发送出去。依赖于各 JAVA App Server 的实现方式,有的将查询 Browser 的 accept-charset 和 accept-language 参数或以其它猜的方式确定 encoding 值,有的则不管。因此采用固定 encoding 也许是最好的解决方法。对于中文网页,可在 JSP 或 Servlet 中设置 contentType="text/html;  charset=GB2312";如果页面中有 GBK 字符,则设置为 contentType="text/html;  charset=GBK",由于 IE 和 Netscape 对 GBK 的支持程度不一样,作这种设置时需要测试一下。因为 16 位 JAVA  char 在网络传送时高 8 位会被丢弃,也为了确保 Servlet 页面中的汉字(包括内嵌的和 servlet 运行过程中得到的)是期望的内码,可以用 PrintWriter out=res.getWriter() 取代 ServletOutputStream  out=res.getOutputStream(). PrinterWriter 将根据 contentType 中指定的 charset 作转换 (ContentType 需在此之前指定! ); 也可以用 OutputStreamWriter 封装 ServletOutputStream 类并用 write(String) 输出汉字字符串。对于 JSP,JAVA Application Server 应当能够确保在这个阶段将嵌入的汉字正确传送出去。
  • 这是解释 URL 字符 encoding 问题。如果通过 get/post 方式从 browser 返回的参数值中包含汉字信息, servlet 将无法得到正确的值。SUN 的 J2SDK 中,HttpUtils.parseName 在解析参数时根本没有考虑 browser 的语言设置,而是将得到的值按 byte 方式解析。这是网上讨论得最多的 encoding 问题。因为这是设计缺陷,只能以 bin 方式重新解析得到的字符串;或者以 hack HttpUtils 类的方式解决。参考文章 2 均有介绍,不过最好将其中的中文 encoding GB2312、 CP1381 都改为 GBK,否则遇到 GBK 汉字时,还是会有问题。 Servlet API 2.3 提供一个新的函数 HttpServeletRequest.setCharacterEncoding 用于在调用 request.getParameter(“param_name”) 前指定应用程序希望的 encoding,这将有助于彻底解决这个问题。
4.3 IBM Websphere Application Server 中的解决方法WebSphere Application Server 对标准的 Servlet API 2.x 作了扩展,提供较好的多语言支持。运行在中文的操作系统中,可以不作任何设置就可以很好地处理汉字。下面的说明只是对 WAS 是运行在英文的系统中,或者需要有 GBK 支持时有效。
上述 c,d 情况,WAS 都要查询 Browser 的语言设置,在缺省状况下, zh,  zh-cn 等均被映射为 JAVA encoding CP1381(注意: CP1381 只是等同于 GB2312 的一个 codepage,没有 GBK 支持)。这样做我想是因为无法确认 Browser 运行的操作系统是支持 GB2312, 还是 GBK,所以取其小。但是实际的应用系统还是要求页面中出现 GBK 汉字,最著名的是朱总理名字中的“?F"(rong2 ,0xe946,\u9555),所以有时还是需要将 Encoding/Charset 指定为 GBK。当然 WAS 中变更缺省的 encoding 没有上面说的那么麻烦,针对 a,b,参考文章 5,在 Application Server 的命令行参数中指定 -Dfile.encoding=GBK 即可; 针对 d,在 Application Server 的命令行参数中指定 -Ddefault.client.encoding=GBK。如果指定了 -Ddefault.client.encoding=GBK,那么 c 情况下可以不再指定 charset。
上面列出的问题中还有一个关于 Tag<%...%>,<%=...%> 中的 JAVA 代码里包含的静态文本未能正确显示的问题,在 WAS 中的解决方法是除了设置正确的 file.encoding, 还需要以相同方法设置 -Duser.language=zh -Duser.region=CN。这与 JAVA  locale 的设置有关。
4.4 数据库读写时的 encoding 问题JSP/Servlet 编程中经常出现 encoding 问题的另一个地方是读写数据库中的数据。
流行的关系数据库系统都支持数据库 encoding,也就是说在创建数据库时可以指定它自己的字符集设置,数据库的数据以指定的编码形式存储。当应用程序访问数据时,在入口和出口处都会有 encoding 转换。对于中文数据,数据库字符编码的设置应当保证数据的完整性 .  GB2312,GBK,UTF-8 等都是可选的数据库 encoding;也可以选择 ISO8859-1 (8-bit),那么应用程序在写数据之前须将 16Bit 的一个汉字或 Unicode 拆分成两个 8-bit 的字符,读数据之后则需将两个字节合并起来,同时还要判别其中的 SBCS 字符。没有充分利用数据库 encoding 的作用,反而增加了编程的复杂度,ISO8859-1 不是推荐的数据库 encoding。JSP/Servlet 编程时,可以先用数据库管理系统提供的管理功能检查其中的中文数据是否正确。
然后应当注意的是读出来的数据的 encoding,JAVA 程序中一般得到的是 Unicode。写数据时则相反。
4.5 定位问题时常用的技巧定位中文 encoding 问题通常采用最笨的也是最有效的办法――在你认为有嫌疑的程序处理后打印字符串的内码。通过打印字符串的内码,你可以发现什么时候中文字符被转换成 Unicode,什么时候 Unicode 被转回中文内码,什么时候一个中文字成了两个 Unicode 字符,什么时候中文字符串被转成了一串问号,什么时候中文字符串的高位被截掉了……
取用合适的样本字符串也有助于区分问题的类型。如:”aa 啊 aa?@aa”等中英相间、GB、GBK 特征字符均有的字符串。一般来说,英文字符无论怎么转换或处理,都不会失真(如果遇到了,可以尝试着增加连续的英文字母长度)。
结束语其实 JSP/Servlet 的中文 encoding 并没有想像的那么复杂,虽然定位和解决问题没有定规,各种运行环境也各不尽然,但后面的原理是一样的。了解字符集的知识是解决字符问题的基础。不过,随着中文字符集的变化,不仅仅是 java 编程,中文信息处理中的问题还是会存在一段时间的。
返回列表