J2EE中的多字节字符处理

    技术2022-05-11  142

     J2EE中的多字节字符处理开发带有多字节字符的J2EE应用作者:Wang Yu译者:observer版权声明:任何获得Matrix授权的网站,转载时请务必以超链接形式标明文章原始出处和作者信息及本声明作者:Wang Yu;observer原文地址:http://www.javaworld.com/javaworld/jw-04-2004/jw-0419-multibytes.html中文地址:http://www.matrix.org.cn/resource/article/43/43954_J2EE_Multibyte_character.html关键词: J2EE;Multibyte;character 摘要  大多数的J2EE服务器都能很好地支持多字节语言(如中文和日文),但这些J2EE服务器和浏览器在支持的方式上是有区别的。当开发者将一些中文(或日文)本地化应用从一种服务器迁移到另一种服务器上时,通常会遇到多字节问题。本文分析了有关多字节字符问题的根源,并给出了一些解决方案和指导原则。作者王越(音)  中文是世界上最复杂、最完备的语言之一。有时我会为自己是个中国人而感到幸运,特别是当我看到我的一些外国朋友为学习这门语言(尤其是写汉字)而绞尽脑汁的时候。可当我用J2EE开发本地化Web应用时,又会感到很不幸。下面我就说说为什么。  尽管Java平台和大多数J2EE服务器都能很好地支持国际化,我在开发中文或日文应用时仍会遇到许多有关多字节字符方面的问题:·编码和字符集之间有什么区别?·为什么多字节字符应用从一种操作系统迁移到另一种上时显示会有差异?·为什么多字节字符应用从一种应用服务器迁移到另一种上时显示会有差异?·为什么我的多字节字符应用在IE浏览器里显示正常,可到了Mozila浏览器里却又不行?·为什么以UTF-16(通用转换格式)编码的应用在大多数J2EE服务器上都不能很好地显示?  如果你也有同样的问题,本文将有助于你找到答案。字符的基础知识  字符在计算机出现之前就早已存在。大约3,000年前,古代中国就出现了一些特殊的文字符号(即甲骨文)。这些文字符号有特定的形状和含义,它们中的大部分都有名字和发音。所有这些文字符号汇集成了字码表,一套从属于特定语言的独特字符集合,它们与计算机没有任何关系。几千年过去了,许多语言都在发展,数以千计的字符被创造出来。如今我们要将所有这些字符都数字化为0和1,这样计算机才能理解它们。  用键盘输入单词的时候要用到字符输入法。对于简单的字符,键盘和字符间存在着一对一的映射关系;而对于较复杂的语言,需要多次敲击键盘才能输入一个字符。  在你能从屏幕上看到字符之前,操作系统必须先将字符存放在内存里。实际上操作系统在字码表的字符与一系列非负整数之间定义了一一对应的关系,它们被存放在内存里并被操作系统调用,这些整数被称为字符代码。  字符可以用文件存储或通过网络传输。软件用字符编码来定义每个字符的字符代码与八进制序列数之间的对应方法(算法)。有些字符代码对应一个字节,如ASCII码;还有一些字符代码需要对应两个或更多的字节(如中文和日文),这种对应关系依赖于不同的字符编码方式。  不同的语言使用不同的字码表,每个字码表都有一些特定的编码方式。在某些情况下,当你选用某种语言时就已经不自觉地选择了某种字符编码方式。例如当你选用中文的时候,在默认情况下你用的可能就是GBK中文字码表及称作GBK的特定字符编码方式。  为了不致混淆,我避免使用字符集这个词。显然,字符集与字码表是同义词。字符集在HTTP Mime(多用途网际邮件扩充协议)页头里被误用,其实这里的“charset(字符集)”是指“encoding(编码)”。  Java的特征之一是字符是16位的,这样就能支持Unicode(一种表示各种语言中许多不同种类的字符的标准方式)。不幸的是,这个特征在开发多字节J2EE应用中也引发了许多问题,本文将就此进行讨论。开发阶段引起的显示问题  J2EE应用开发包括若干个阶段(如图1所示),每个阶段都可能导致多字节字符显示问题。图1 J2EE应用开发生命周期编码阶段  当你开始J2EE应用编码时,大多数情况下你会用JBuilder、NetBean之类的IDE,或者是UltraEdit、Vi之类的编辑器。无论你选择了哪一个,只要在JSP(JavaServer Pages)、Java或HTML文件中有文字字符串,而且这些字符串是像中文或日文这样的多字节字符,那么你要是不小心的话就很可能会遇到显示问题。  文字字符串是存储于文件中的静态信息,不同语言的字符采用不同的编码方式。大多数IDE的默认编码方式是ISO-8859-1,这种编码方式适合ASCII字符,但会使多字节字符丢失信息。例如,中文版的NetBean在对文件编码时其默认编码方式就不幸为ISO-8859-1。在我编写带有中文字符的JSP文件时(如图2所示),看上去一切正常。我前面提到,屏幕上显示的所有字符都在内存中,与编码方式没有直接的关系。在保存文件后,如果关闭IDE再重新打开,这些字符就显示为乱码(如图3所示),这是因为ISO-8859-1编码方式在存储中文字符时会丢失一些信息。图2 NetBeans里的中文字符   图3 中文字符成了乱码字符编码API  在servlet和JSP规范里有几个API用来控制J2EE应用的字符编码过程。对于servlet请求,setCharacterEncoding()方法对当前HTTP请求设定编码方式;对于servlet响应,setContentType()方法和setLocale()方法对HTTP响应输出设置Mime头的编码方式。  这些API本身不会引发问题,但如果你忘了用它们就有问题了。例如在有些服务器上你可以正确无误地显示多字节字符而不必在代码中使用上述的任何一个API,但在其它的服务器上运行应用时字符却变成了乱码。多字节字符显示问题的成因在于服务器在处理HTTP请求和响应期间如何对字符进行编码。以下是服务器确定请求和响应的编码方式的规则,对大多数服务器都适用:  在处理servlet请求时,服务器按以下次序(自上而下)来确定请求的字符编码方式:·        代码中指定的设置(如setCharacterEncoding()方法中指定的编码方式)·        厂商的初始设置·        默认的设置  在处理servlet响应时,服务器按以下次序(自上而下)来确定响应的字符编码方式:·        代码中指定的设置(如setContentType() 方法和setLocale()方法中指定的编码方式)·        厂商的初始设置·        默认的设置  按照上述规则,如果在代码中用API进行了指定,所有的服务器都会按指定的字符编码方式编码,否则服务器就会各行其道。有些厂商用HTTP表单的隐藏字段(hidden fields)来确定请求的编码方式,还有些厂商则采用它们自己配置文件中的特定设置。即使是默认设置也不尽相同,大多数厂商采用ISO-8859-1作为默认设置,还有少数厂商采用操作系统的本地设置值。因此,一些带有多字节字符的应用在迁移到另一个厂商的J2EE服务器上时就会出现显示问题。编译阶段  如果设置正确,在编辑的时候就能在源文件中存储多字节的文字字符串,但这些源文件不能直接执行。如果编写的是servlet代码,这些Java文件在部署到应用服务器之前必须先被编译成类文件。对于JSP文件,应用服务器在执行前会自动将其编译成类文件。在编译阶段,字符编码问题仍有可能存在。为了运行下面这个简单示例,请下载本文的源代码。程序清单1 EncodingTest.java

    1      import java.io.ByteArrayOutputStream;2      import java.io.OutputStreamWriter;34      public class EncodingTest {  5         public static void main(String[] args) {6            OutputStreamWriter out = new OutputStreamWriter(new ByteArrayOutputStream());7            System.out.println("Current Encoding:  "+out.getEncoding());8            System.out.println("Literal output:  ÄãºÃ£¡"); // You may not see this Chinese String9         }10     }   有关这段源代码的说明如下: ·        我们用下面的代码确定系统当前的编码方式: 6    OutputStreamWriter out = new OutputStreamWriter(new ByteArrayOutputStream());7    System.out.println("Current Encoding:  "+out.getEncoding()); ·        第8行包含直接打印输出中文文字字符串(由于操作系统语言设置的原因可能造成该字符串不能正常显示)的代码。 ·        用GBK编码方式保存这个Java源文件。   执行结果如图4所示。 图4 示例程序的输出。   从图4的执行结果中我们可以归纳出: ·        Java编译器(javac)将系统的语言环境作为默认的编码设置,Java运行时(Java Runtime Environment.)也如此。 ·        只有第一次的运行结果是正确的,其它的字符串显示都有问题。 ·        仅当运行时的编码设置与源文件保存时的编码方式相一致时才能正确显示多字节文字字符串(否则就必须进行转码,参见“运行时阶段”部分)。 服务器配置阶段   在运行J2EE应用之前,一般会根据特定的需要对应用进行配置。在上一节中,我们发现不同的语言设置会导致文字字符串显示出问题。实际上配置存在于不同的层面,它们都可能会引发多字节字符问题。 操作系统层   操作系统对语言的支持非常重要。前面提到服务器端对语言的支持会影响JVM默认的编码设置,而在客户端的语言支持(如字体)也能直接影响字符的显示,但这不是本文要讨论的重点。 J2EE应用服务器层   大多数服务器都有一个基本服务器设置,可用来配置默认的字符编码处理方式。清单2就是Tomcat配置文件的一部分(位于$TOMCAT_HOME/conf/web.xml)。 清单2 web.xml <servlet>        <servlet-name>jsp</servlet-name>        <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>        <init-param>            <param-name>fork</param-name>            <param-value>false</param-value>        </init-param>        <init-param>            <strong>            <param-name>javaEncoding</param-name>>            <param-value>>UTF8</param-value>            </strong>        </init-param>        <load-on-startup>3</load-on-startup>  </servlet>   Tomcat以参数javaEncoding来确定从JSP文件生成Java源文件的Java文件编码方式。这里的默认值是UTF-8,这意味着如果JSP文件中的中文字符以GBK编码保存,将会以UTF-8编码(浏览器端设置)显示,在这种情况下就可能会出问题。 JVM层   大多数服务器都允许同时运行多个实例,且每个服务器实例都能有自己的JVM实例。此外,还可对每一个JVM实例分别设置。大多数服务器用本地设置来为每个实例定义默认的语言支持。 图5 Sun ONE 应用服务器设置   图5显示的是Sun ONE(开放网络环境)应用服务器的一个本地单个实例设置。该设置给出了登录系统和标准输出的默认字符编码方式。   此外,不同的服务器使用的JVM版本可能会不同,而不同的JDK版本支持的编码标准各异,所有这些都会导致迁移问题。例如Sun ONE应用服务器与Tomcat都支持J2SE 1.4,而有些服务器只支持到J2SE 1.3。J2SE 1.4支持Unicode 3.1,它具有许多早期版本所没有的新特性。 单个应用层   每个部署在服务器上的应用在运行前都可以为其配置独立的编码设置,这就使得在同一个服务器实例上能够运行多个采用不同语言的应用。一些服务器用以下的字符编码设置为每个部署的应用指定其应使用的编码方式: <locale-charset-info default-locale="en_US">      </locale-charset-map locale="zh_CN" agent="Mozilla/4.77 [en] (Windows NT 5.0; U)"  charset="GBK"></locale-charset-info>   这种分层配置的目的是为了灵活性和可维护性。但不幸的是,当在服务器间迁移时这种做法就可能导致出问题,因为并非所有的服务器配置都遵循标准。比如说,如果在一个支持本地字符集设置的服务器上开发了应用,那么当把该应用迁移到另一个不支持这种编码设置的服务器上时就可能会遇到问题。 运行时阶段   在运行过程中J2EE应用很可能会与其它外部系统通信。应用也许会读写文件,或者用数据库管理数据,有时候还可能用LDAP(轻量目录访问协议)服务器存储标识信息。在这些情况下,J2EE应用和外部系统之间需要进行数据交换。如果数据中带有象中文这样的多字节字符,就可能会遇到问题。   大部分的外部系统都有他们自己的编码设置。例如LDAP服务器很可能使用UTF-8对字符编码;Oracle数据库系统用环境变量NLS_LANG来指定编码方式。如果Oracle是安装在中文操作系统上,该变量的默认设置为ZHS16GBK,也就是用GBK编码方式来存储中文字符。因此当J2EE应用的编码设置与外部系统不同时需要进行转码,通常用以下代码来完成这一工作: byte[] defaultBytes = original.getBytes(current_encoding);String newEncodingStr = new String(defaultBytes, old_encoding);   以上代码给出了如何将字符串从一种编码方式转换为另一种。例如你在LDAP服务器中用UTF-8编码存储了一个用户名(多字节字符),而在J2EE应用中用的却是GBK编码,因此当应用从LDAP服务器中取用户名时就可能被错误地编码。要解决这个问题,可以用original.getBytes("GBK")得到原始的字节,然后用new String(defaultBytes, "UTF-8")构造一个新字符串,这样就可以正确显示了。 客户端显示阶段   现在大多数J2EE应用都采用浏览器/服务器架构,以浏览器作为客户端。要在浏览器里正确显示多字节字符,需要注意以下几个方面: 浏览器语言支持:   为能正确地显示多字节字符,浏览器及其所运行的操作系统应提供对特定语言的支持,比如字体和字码表。 浏览器编码设置   服务器返回的HTML头(<meta http-equiv="content-type" content="text/html;charset=gb2312">)向浏览器声明了该页面使用的编码方式,否则浏览器将使用默认编码设置或自动进行匹配。当然,用户也可以对页面的编码进行设定,如图6所示。 图6 Netscape的编码设置页   因此如果页面没有声明,多字节字符就可能显示不正确,在这种情况下用户必须手工设定当前页面的编码方式。 HTTP POST编码   用HTML页面的Form标签向服务器提交数据会使情况变得更为复杂。浏览器的编码方式取决于当前页面的编码设定,对Form标签也照此处理。这意味着如果ASCII格式的HTML页面用ISO-8859-1编码,那么用户在此页面中将不能提交中文字符。这是因为所有提交的数据都用ISO-8859-1编码,这将使中文字符丢失字节。所有的浏览器都遵守这个HTML标准。 HTTP GET编码   URL链接中带有多字节字符会使事情复杂化,像<A href = getuser.jsp?name=**>View detail information of this user</A>(**代表多字节字符)。这种情况很常见,例如在链接里加入用户名或其它信息以便传给下一页。但RFC (因特网标准草案) 2396中并未明确规定URL中有非US-ASCII字符时的格式,不同的浏览器会采用它们自己的方式来编码URL中的多字节字符。   以Mozila为例(如图7/8/9/10),通常是在HTTP请求发送前对URL编码。我们知道在URL编码过程中,首先根据某种编码方式(如UTF-8或GBK)将一个多字节字符转换成两个或更多的字节,然后每个字节用3个字符组成的字符串%xy来表示,其中xy是表示该字节的两个十六进制数。这方面的更多信息可参考HTML规范。不管怎样,URL编码所采用的编码方式取决于当前页面的编码方式。   我用下面这个gbk_test.jsp页面做演示: 清单3 gbk_test.jsp <%@page contentType="text/html;charset=GBK"%><HTML>   <BODY>      <a href='/chartest/servlet/httpGetTest?name=王'><h1>Test for GBK encoded URL</h1></a>   </BODY></HTML>   x738b是一个中文字符的转义值,这个中文字符就是我的姓。该页面如图7所示。 图7 Mozilla的URL   当鼠标移动到该链接上时,链接的地址就会在状态栏中显示出来,可以看到URL中嵌入了一个中文字符。点击页面中的链接,可以从地址栏中清楚地看到该字符已被URL编码。字符x738b被编码为
    转载请注明原文地址: https://ibbs.8miu.com/read-5461.html

    最新回复(0)