Web容器的编码问题

 

<!--[if !supportLists]-->一、      <!--[endif]-->概述

基于 Java 编程语言进行应用开发时,不可避免地要处理中文。Java 编程语言默认的编码方式是 UNICODE,而我们通常使用的数据库及文件都是基于 GBK 编码的,我们经常碰到这样的情况:浏览基于 JSP 技术的网站看到的是乱码,文件打开后看到的也是乱码,被 Java 修改过的数据库的内容在别的场合应用时无法继续正确地提供信息。

 

本文将从编码基本知识,Java对字符的处理,以及常见问题的解决三个方面对问题进行阐述。

<!--[if !supportLists]-->二、      <!--[endif]-->编码基本知识

最早的编码是ISO-8859-1,和ASCII编码相似。但为了方便表示各种各样的语言,逐渐出现了很多标准编码,重要的有如下几个。 

1、ISO-8859-1 

属于单字节编码,最多能表示的字符范围是0-255,应用于英文系列。比如,字母'a'的编码为0x61=97

很 明显,ISO-8859-1编码表示的字符范围很窄,无法表示中文字符。但是,由于是单字节编码,和计算机最基础的表示单位一致,所以很多时候,仍旧使用 ISO-8859-1编码来表示。而且在很多协议上,默认使用该编码。比如,虽然"中文"两个字不存在ISO-8859-1编码,以gb2312编码为例,应 该是"d6d0 cec4"两个字符,使用ISO-8859-1编码的时候则将它拆开为4个字节来表示:"d6 d0 ce c4"(事实上,在进行存储的 时候,也是以字节为单位处理的)。而如果是UTF编码,则是6个字节"e4 b8 ad e6 96 87"。很明显,这种表示方法还需要以另一种编码为基础。 

2、GB2312/GBK 

这就是汉子的国标码,专门用来表示汉字,是双字节编码,而英文字母和ISO-8859-1一致(兼容ISO-8859-1编码)。其中GBK编码能够用来同时表示繁体字和简体字,而gb2312只能表示简体字,GBK是兼容gb2312编码的。 

3、UNICODE 

这是最统一的编码,可以用来表示所有语言的字符,而且是定长双字节(也有四字节的)编码,包括英文字母在内。所以可以说它是不兼容ISO-8859-1编码 的,也不兼容任何编码。不过,相对于ISO-8859-1编码来说,UNICODE编码只是在前面增加了一个0字节,比如字母'a'"00 61" 

需要说明的是,定长编码便于计算机处理(注意GB2312/GBK不是定长编码),而UNICODE又可以用来表示所有字符,所以在很多软件内部是使用UNICODE编码来处理的,比如java 

4、UTF -8

考虑到UNICODE编码不兼容ISO-8859-1编码,而且容易占用更多的空间:因为对于英文字母,UNICODE也需要两个字节来表示。所以 UNICODE不便于传输和存储。因此而产生了UTF-8编码,UTF-8编码兼容ISO-8859-1编码,同时也可以用来表示所有语言的字符,不过,UTF-8编码 是不定长编码,每一个字符的长度从1-6个字节不等。另外,UTF-8编码自带简单的校验功能。一般来讲,英文字母都是用一个字节表示,而汉字使用三个字节。  

注意,虽然说UTF-8是为了使用更少的空间而使用的,但那只是相对于UNICODE编码来说,如果已经知道是汉字,则使用 GB2312/GBK无疑是最节省的。不过另一方面,值得说明的是,虽然UTF-8编码对汉字使用3个字节,但即使对于汉字网页,UTF-8编码也会比 UNICODE编码节省,因为网页中包含了很多的英文字符。 

<!--[if !supportLists]-->三、      <!--[endif]--> java对字符的处理 

java应用软件中,会有多处涉及到字符集编码,有些地方需要进行正确的设置,有些地方需要进行一定程度的处理。 

1、 getBytes(charset) 

这是java字符串处理的一个标准函数,其作用是将字符串所表示的字符按照char set编码,并以字节方式表示。注意字符串在java内存中总是按 UNICODE编码存储的。比如"中文",正常情况下(即没有错误的时候)存储为"4e2d 6587",如果char set"GBK",则被编码为 "d6d0 cec4",然后返回字节"d6 d0 ce c4"。如果char set"UTF-8"则最后是"e4 b8 ad e6 96 87"。 如果是"ISO-8859-1",则由于无法编码,最后返回 "3f 3f"(两个问号)。 

3、new String(charset) 

这是java字符串处理的另一个标准函数,和上一个函数的作用相反,将字节数组按照charset编码进行组合识别,最后转换为UNICODE存储。参考上述getBytes的例子,"GBK" "UTF-8"都可以得出正确的结果"4e2d 6587",但ISO-8859-1最后变成了 "003f 003f"(两个问号)。 

因为UTF-8可以用来表示/编码所有字符,所以new String( str.getBytes( "UTF-8" ), "UTF-8" ) ==str,即完全可逆。 

4、setCharacterEncoding() 

该函数用来设置http请求或者相应的编码。 值得注意的是这个函数只设置了Http协议body里面的编码,而对请求连接和Http头无效。因此,该指定只对POST方法有效,对GET方法无效。

对于request,是指提交内容的编码,指定后可以通过getParameter()则直接获得正确的字符串,如果不指定,则默认使用ISO-8859-1 编码,需要进一步处理。值得注意的是在执行setCharacterEncoding()之前,不能执行任何 getParameter()。对于response,则是指定输出内容的编码,同时,该设置会传递给浏览器,告诉浏览器输出内容所采用的编码。 

5、几处设置 

对于web应用程序,和编码有关的设置或者函数如下。 

<!--[if !supportLists]-->l        <!--[endif]-->JSP编译 

指定文件的存储编码,很明显,该设置应该置于文件的开头。例如:<%@page pageEncoding="GBK"%>。另外,对于一般class文件,可以在编译的时候指定编码。 

<!--[if !supportLists]-->l        <!--[endif]-->JSP输出 

指定文件输出到browser是使用的编码,该设置也应该置于文件的开头。例如:<%@ page contentType="text/html;charset= GBK" %>。该设置和response.setCharacterEncoding("GBK")等效。 

<!--[if !supportLists]-->l        <!--[endif]-->META设置 

指定网页使用的编码,该设置对静态网页尤其有作用。因为静态网页无法采用JSP的设置,而且也无法执行 response.setCharacterEncoding()。例如:<META http-equiv="Content- Type" content="text/html; charset=GBK" /> 

如果同时采用了JSP输出和meta设置两种编码指定方式,则JSP指定的优先。因为JSP指定的直接体现在response中。 

<!--[if !supportLists]-->四、      <!--[endif]-->常见问题的解决

常见的编码问题大致可以分为四类:第一类是在编译期间造成,原因在于编写代码的编码格式和编译器使用的编译格式不一致;第二类是在运行时由于编码设置的不正确造成,主要因为没有正确设置requestresponse的字符集编码格式,输入与输出使用了不同的编码格式。第三类是使用URL传输数据时造成,由于HTTP协议规范并没有考虑多国文字,因此URL信息并不包含编码格式信息,编码格式只能依靠约定的形式来确定。第四类由引入资源文件造成,因为引入的资源文件编码格式与页面输出格式不同或没有指定引入资源的编码格式,使得引入的资源文件不能正确的确定编码格式。

4.1编译问题

4.1.1 编译器字符集指定

对于Java类文件的编译问题并不常见,但是错误的本地编码可能会引起这类问题。编译Java文件时会使用Javac(标准Java 编译器)进行编译,它使用的字符集为操作系统默认的字符集,比如在中文 Windows 操作系统上是 GBK/GB18030 ,而在英文UNIX 操作系统上就是ISO-8859-1。因此开发人员会发现在 英文UNIX 操作系统上编译的类,其中双字节(中文)字符都成了乱码或者问号。解决方案是在编译时添加 encoding 参数,就可以做到与平台无关。语法为:javac -encoding GBK

对于JSP文件,情况就不太一样了,应为容器在运行时才会对JSP做出解析,生成相应的class文件。如果不对文件做出编码的指定,容器则会使用默认的方式进行编码。此时,编码问题就有可能出现。解决的方法是指定页面使用的编码方式,如:<%@page pageEncoding="GBK"%>。这样,容器便会知道使用何种编码对文件进行解析,运行环境发生改变后也不会出现编码的异常。

4.2.3 使用Include引入

可以使用<%@include%> <jsp:include>两种方法引入另一个JSP页面。这两种方法引入的机制不同,前者使用的是静态引入的方式,在编译时它会将带引入的JSP页面同时生成在同一个class文件中。而后者是采用的动态引入方式,只有在页面请求时才会动态的调用另一个JSP页面。因为,它们引入的方式不同,因此,在处理这两种情况时解决的方法也不相同。

使用<jsp:include>时,由于引入的JSP页面是一个独立页面,因此可以和普通JSP页面一样进行编码和输出的指定。

但是在使用<%@ include %>时,被引入的文件不能像被<jsp:include>引入的文件一样通过加入指定页面和输出编码的代码片段,因为,如果加入这段代码在解析 JSP文件时就会因为contentType内容重复而报错。但是,可以通过仅指定页面编码格式“<%@  page language="java" pageEncoding="GBK"%>”来解决中文问题。此时只指定了页面采用的编码方式,而没有制定页面contentType的内容,不会和主页面冲突。

另外,如果使用<jsp:include>包含文件时同时传递了包含汉字的参数,也有可能出现编码问题。解决这个问题的方法将在4.2.2节“Forward中问题”中进行介绍。

4.2编码设置问题

    编码设置问题产生的主要问题是没有正确设置requestresponse的字符集编码方式。解决的办法很多,可以在有可能产生问题的地方插入设置字符编码的代码片段,也可以通过使用Filter设置字符编码。本节首先介绍此类问题中常见的情况,以及它们的解决方法,最后介绍如何使用Filter统一解决编码设置问题。

4.2.1显示问题

JSP页面能正确地被编译并不意味着最终显示给用的内容是正确的。因为生成的页面内容还必须经过网络传输才能到达用户的浏览器,而这个过程中也存在着编码的转换问题。如果不对response进行设置,容器会使用默认的编码方式进行传送,而客户端浏览器也可能获取不到编码信息,最终用户看到的可能就是无法辨识的乱码。此时,用户可以通过调整浏览器的字符集来进行调整,但是这决不是用户所希望的。

解决的方法很简单,只要指定JSP页面输出格式便可。语法为:<%@ page language="java" contentType="text/html; charset=GBK"%>。此标签与使用response.setCharacterEncoding()是等效的。

综合前两个问题,对于一个JSP页面如果需要解决编译和显示的问题可以通过同时指定页面的编码格式和输出格式达到。语法为:<%@ page language="java" contentType="text/html; charset= GBK" pageEncoding="GBK"%>。需要指出的是charsetpageEncoding并一定需要指定为相同的字符集,可以根据具体需要决定。比如说一个页面需要使用GBK编写,同时又希望以UTF-8的格式输出,就可以指定为<%@ page language="java" contentType="text/html; charset= UTF-8" pageEncoding="GBK"%>

4.2.2 Form提交问题

当浏览器提交表单的时候,可以指定相应的编码。例如:<form method="POST" accept-charset= "GBK">。一般不必不使用该设置,使用POST方法时浏览器会直接使用网页的编码。 

Form一般推荐使用POST方法提交请求。因为这种方法相比GET方法可以提交更多的内容,它不会受到请求参数的限制,而且更容易解决编码问题。对于如何解决GET方法里面的编码问题留在RUL编码一节介绍。

4.2.3 Forward中文问题

使用“jsp:forward”标签进行页面跳转,如果没有指定requestcharcterEncoding则很有可能发生编码问题。在处理Forward请求时,如果requestencoding为空,容器便会使用ISO-8859-1的默认编码方式。GBK格式保存得数据并不能直接转换为ISO-8859-1格式,因为GBK是双字节编码,而ISO-8859-1是单字节编码,所以转换的过程中会丢失必要的信息。比如“中文”的GBK编码应该为“%d6%d0%ce%c4UTF-8编码为“%e4%b8%ad%e6%96%87,但如果直接用ISO-8859-1编码格式进行转换,那么转换的结果为“%3f%3f”,丢失掉了必要信息,无法进行反编码操作。解决问题的方式只能是重定向前首先设置request的编码格式,如request.setCharacterEncoding("GBK")

值得注意的是在跳转后的页面便可以直接获得真确的编码,但是如果在获取参数前将字符集改为另外的格式,则同样会引起编码异常。因为改变编码集后,造成了编码前的字符集和解编码的字符集不匹配。

4.2.4 使用Filter

在每个有可能产生编码问题的地方设置字符集编码,既不便于页面的编码与设计,也不利于后期的维护。由于Filter会在servlet调用前被调用,所以可以使用Filter统一解决编码设置的问题,这将大大减轻编码和维护的工作量。

用于设置请求字符集编码的Filter的功能可以设计得非常简单,只需要为每个请求设置特定的编码格式便可,当然,可以根据应用的需要设计的相对比较复杂,也可以对不同的模块使用不同的过滤器,解决多国语言问题。其核心逻辑可以简化为如下形式:

public void doFilter(...) throws IOException, ServletException {

      request.setCharacterEncoding( “GBK”);

}

4.3 URL编码问题

 URL的信息会先于HTTP请求中的其它信息被解析,所以即便使用4.2节中的方法进行了正确的设置仍有可能会产生编码问题。

 URL中包含请求的路径信息和参数信息两个部分,通过设置服务器的URI解析的默认编码或重构字符串都可以解决这类问题。不过,参数的处理和路径的处理在不同的浏览器间会存在细微的差别,因此将会分开介绍它们各自的解决方法。

4.3.1 URL参数问题

       URL请求所包含的参数只能使用容器的默认字符集解析,requestsetCharacterEncoding函数所设置的编码格式并不能影响到参数的读取,所以当请求中包含汉字等信息时便会出现乱码的现象。

       可以通过设置服务器的URIEncoding属性解决这个问题。但是这种解决方法和下一节所介绍的URL路径问题可能存在一定的冲突,下一节将详细分析这种情况。

       另外,也可以通过程序代码显示的对数据进行解编码。此时需要确定容器的默认编码方法和待转化的目标编码方式。在不设置容器默认编码的情况下,容器会使用ISO-8859-1编码格式作为默认编码格式。如果应用使用的是GBK的编码格式,编码转化代码可以写为:

new String(request.getParameter(name).getBytes(“ISO-8859-1”),”GBK”)

4.3.2 URL路径问题

       处理URL路径中的汉字就不像处理参数那么容易了,不是简单的对服务器进行设置或者编写字符集转化代码就可以解决问题的。

由于IE浏览器有一个选项“总是以UTF-8发送URL”,并且在默认的情况下是选中的。因此,使用IE浏览页面时,无论用户使用什么样的编码,URL路径默认都会以UTF-8的编码格式处理。但是,URL中参数信息的编码格式并不一定是UTF-8的格式,而是根据用户使用的编码确定的。这样,如果试图通过设置服务器的URIEncoding解决编码问题就很可能会和URL参数中的编码冲突。用户可能希望使用GBK读取参数信息,同时又希望使用UTF-8读取路径信息。这种设置几乎是不可能的,况且,其它浏览器对路径信息编码的格式与IE存在着差异,即便某种服务器具有这种功能也很难彻底解决这种冲突。

如果希望通过服务器的设置彻底解决URL的编码问题,应用就必须约定使用UTF-8的格式输出,好在这种格式对多国语言的支持是很有利的,便于应用的国际化操作,同时也可以避免因为IE浏览器的特殊默认设置造成的影响。值得注意的是输出格式为UTF-8,并不影响使用GBK编写页面,可以通过contentTypepageEncoding连个不同的属性进行指定。

请求ServletJSP的路径一般都不会出现中文,也几乎没有人这么设计程序,虽说Java支持这种编写方式。有可能出现汉字的地方大部分情况下是访问某些资源文件,比如说一个word文件。这种情况下如果仍然希望使用GBK作为应用输出,可以编写一个特殊的servlet来处理这类请求。

Servlet中需要处理的核心问题是如何确定URL中使用的编码方式。直接通过字符串精确判断字符编码格式几乎是不可能的。考虑到请求的是应用中能找到的资源,因此可以通过编码转换加上搜索资源是否存在来确定编码的格式。确定编码格式的代码如下:

for (int i = 0; i < encoding.length; i++) {

String path = new String(pathInfo.getBytes(defaultEncoding),

              encoding[i]);

if (context.getResource(path) != null) {

       pathInfo = path;

       break;

}

}

       其中,pathInfo是当前访问的路径信息,defaultEncoding是服务器使用的默认编码格式,encoding是一个待搜索的字符集数组。当context.getResource(path) 不为空时说明当前的路径有对应的资源存在,也就是说找到了合适的字符集对请?/div>

 

 

转自 http://blog.zol.com.cn/853/article_852554.html

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值