java中文编码(字符集)分析-中文乱码分析及解决方案




注:本文部分内容摘自网络,摘抄内容版权归原作者所有。 

1.      背景知识

1.1.     Http协议

1.1.1.   URLURI

 

1.1.2.   媒体类型定义

HTTP Content-Type14.17 节)和 Accept14.1 节)头部域中使用因特网媒体类型 [17],为了提供打开和可扩展的数据类型和类型协议。

media-type = type "/" subtype *( ";" parameter ) 

type = token

subtype = token
parameter
【可以】 接在 type/subtype 后面, attribute/value 对的形式(如 3.6 节中所定义)。
type
subtype parameter 属性名是大小写非敏感的。 parameter 值可以是或不是大小写敏感的, 取决于 parameter 名称的语义。【禁止】在 type subtype 间使用线性空白符(LWS),属性与其值间也禁止。 存在或缺少 parameter 可以对 media-type 处理有意义, 取决于媒体类型注册表中的定义。

要注意, 一些老的 HTTP 应用程序不认识媒体类型参数。 当发送数据给老 HTTP 应用程序时, 实现【应该】只在该 type/subtype 定义需要时使用媒体类型的参数。

media-type 的值由因特网分配数字权威(IANA [19])注册。 媒体类型注册过程在 RFC

1.1.3.   和字符集相关的指令

       jspservlethtml,指定IE按照那一种字符集,解析字节流。即:IEhtml页面的默认页面编码。

  • JSP文件中ContentType,指定传输内容的编码格式。如下:

           <%@ pagecontentType="text/html;charset=GBK"%>

  • Java Servlet中,在response对象中设置。如下:

         response.setContentType("text/html;charset=GBK");

  • HTML中,通过<meta http-equiv>元素设置。如下:

    <META HTTP-EQUIV="Content-Type" CONTENT="text/html;CHARSET=GBK">

2.      的编码

“火” 字,

UTF-8 编码:0xE781AB

GBK 编码:0xBBF0

GB2312编码:0xBBF0

3.      在地址栏中输入地址,提交

3.1.     涉及参数

IE选项à高级:

3.2.     “以UTF-8发送”选中,不带中文参数

IE选项à高级:

地址中出现中文,例如:

浏览器把中文“火”字,先按照UTF-8编码转译,如下:

把这个地址转ISO-8859-1编码;下图,浏览器提交字节流,二进制编码;如下:

服务器端响应的二进制流编码:

3.3.     “以UTF-8发送”选中,带中文参数

IE选项à高级:

地址,方式:js调用、地址栏输入。

浏览器把url地址中“火”字,先按照UTF-8编码转译,?后参数的火字,按照页面编码转码(页面编码GBK),:

 

 

过程为:url地址处理,将中文字符转为%的形式,将转译后的字符串作为新的地址字符串;将地址和参数编译为字节流,地址按照ISO-8859-1编码,参数按照gbk编码(页面编码)。

 

当页面字符集时UTF-8时,提交字节流,如下:

参数编码为UTF-8编码。

 

3.4.     “以UTF-8发送”不选中

IE选项à高级:

地址中出现中文,例如:

提交字符串,直接包括“火”字。

浏览器提交字节流,二进制编码:

“火”字的编码bbf0GBK字符集格式的编码。

服务器端响应的二进制流编码:

此时的过程为:

浏览器将“火”字按照GBK编码 bbf0 的字节流发送到服务器端,服务器端默认按照ISO-8859-1解析该字节流,服务器端进行处理(解析的目的是转换为UTF-16的内部编码,注意:这里是以字符为基准的转换,因为二进制字节流是没有意义的,只有转换为字符后,才能找到utf-16中对应的编码,或者说,是把字符iso-8859-1对应的码值转换为utf-16中对应的码值),处理完毕后按照UTF-8字符集转换字节流编码(c2bbc3b0),输出到客户端。注意utf-16转换是无损的。当然如果出现,gbk转换为iso8859-1出现信息损伤是不可恢复的。

结论:

不选中时,URL地址中的汉字,按照页面字符集进行编码。

3.5.     结论

地址栏中输入的URL地址字符串,在提交给服务器端时,对URL分成两块单独编码;“?”前的地址为一部分;“?”后的参数为一部分。

地址部分,在“以UTF-8”选中时,地址部分出现的多字节字符,按照UTF-8转码;如果未选中,按照操作系统默认字符集进行编码。

参数部分,和“以UTF-8”参数无关,按照操作系统默认字符集进行编码。

4.      通过js方式提交地址

4.1.     URL地址不转码提交

  1. 不转码和get方式相同。例如:

  2. 提交URL

    原始URL

    页面编码为UTF-8

  3. 提交到Web服务器端,

    URL编码

    查看userid后的编码

    0xe781,编码格式是UTF-8,和页面字符集一致。

  4. 结论

    和在地址栏直接输入URL一致。

4.2.     URL地址转码提交

URL地址转码,通过调用encodeURIencodeURIComponent函数,对整体的URL地址(包括参数)中非ANSI字符(见encodeURI方法的描述)进行编码,编码字符集为UTF-8(指定其他字符集也无效),在URL中显示的格式为,%+十六进制的字符序列。

原始URL

转码后的URL

提交到Web服务器中的URL(拦截得到):

4.3.     转码提交后解析过程

在上述两种情况中,应用服务器默认编码为ISO-8859-1,服务器不能正确解析该URI。结果如下:

未转码:

转码:

 

解析过程

应用服务器(Tomcat)对地址中的%E7%81%AB进行解析过程为,去掉%号,将%后的字符作为HEX数,根据Tomcat设置的字符集翻译为字符。Tomcat默认字符集为ISO-8859-1,使用ISO-8859-1E781翻译为字符,发现E781超出ISO-8859-1的代码空间,不识别该字符,翻译为“?”(即0x3f)。

        解决方法,(一)将应用服务器字符集设置UTF-8;(二)将对字节序列的转码放到程序中实现。

        方法(一)在server.xml中的<connect/>元素中,添加URIEncoding=”UTF-8”属性。

        方法(二)在客户端二次转码encodeURI(encodeURI(url)),此时url字符串为:

详见下面样例。注意,此方法,只能对URL字符串中参数部分解析,对URI中地址部分不能解析。

4.4.     转码提交解析样例

4.4.1.   环境设置

环境设置

目标文件所在目录:urlencode\\urlencode-2.jsp

IE页面字符集:UTF-8

IE选项:选中“以UTF-8发送

request编码设置:

 

4.4.2.   应用服务器设置UTF-8字符集

  1. 设置URIEncodingUTF-8

    Tomcat配置文件设置URI解析字符集

  2. 过程

    通过js请求的URL

    提交到服务器端的地址串(截获):

    结果:成功。

    成功页面显示的地址:

     

  3. 结论:

    Tomcat应用服务器设置的字符集对URI进行解码。

4.4.3.   二次转码和二次解码

  1. URL地址中不包含中文

  1. 请求的URL

  2. IE编码后提交服务器端的字符串

  3. 直接从request中获取的值

  4. 通过URLDecode类解码后的值

  1. URL地址中包含中文

  1. 请求的URL

  2. IE编码后提交服务器端的字符串

  3. 失败页面

4.5.     结论

js提交的URL地址字符串,在提交给服务器端时,对URL分成两块单独编码;“?”前的地址为一部分;“?”后的参数为一部分。

地址部分,在“以UTF-8”选中时,地址部分出现的多字节字符,按照UTF-8转码;如果未选中,按照页面默认字符集进行编码。

参数部分,和“以UTF-8”参数无关,按照页面默认字符集进行编码。

5.      服务端解析

  • URI的解析

            URI的解析包括两部分,地址部分和参数部分。应用服务器根据指定的字符集解析URI中的地址部分和参数部分,即以GET方式提交的数据。

            注意二次编码和二次解析,只能解析参数,不能解析URI

  • 对提交数据内容的解析

            GET方式提交的数据,根据应用服务器的字符集解析。在应用中设置request.setCharacterEncoding()不起作用。

            Post方式提交的数据,根据request.setCharacterEncoding()确定的字符集解析。默认字符集为Java默认的字符集,java默认的字符集为操着系统默认的字符集。

6.      URI允许的字符

ASCII字母、数字、保留字符、标记字符

7.      结论

7.1.     在地址栏输入URL,编码和解码

  1. URL地址部分(“?之前”):

  • 编码

            选中“以UTF-8发送”,非多字节序列的字符,被转译为UTF-8字节序列。例如,汉字被转译为%A1%B0格式,其中A1是汉字对应的UTF-8编码,让后整个URL按照ISO-8859-1编码发送到服务器端。服务器端以ISO-8859-1解码。

            未选中“以UTF-8发送”,URL地址的编码,按照操作系统默认字符集进行编码。

  • 解码

            服务器端默认按照iso-8859-1解码,可以手工修改应用服务器的URI解析字符集

  1. URL地址后的参数(?后的部分)

  • 编码:

            按照操作系统默认字符集进行编码。

  • 解码:

            Tomcat中,服务器端默认按照iso-8859-1解码,不会使用request.setCharacterEncode()方法设置的字符集解码。

7.2.     js方式提交URL,编码和解码

  1. URL地址部分(“?之前”)

    编码

            选中“以UTF-8发送”,和在地址栏输入URL相同。如果js做了转码,URL字符串则是符合URL规范的字符串。

            未选中“以UTF-8发送”,URL地址的编码,按照IE页面默认字符集进行编码。

    解码

            服务器端默认按照iso-8859-1解码,可以手工修改应用服务器的URI解析字符集。将%的字节序列,转换为字符。

  2. URL地址后的参数(?后的部分)

    编码:

            按照IE页面默认字符集进行编码。如果js做了转码,URL字符串则是符合URL规范的字符串。

    解码:

            Tomcat中,服务器端默认按照iso-8859-1解码,不会使用request.setCharacterEncode()方法设置的字符集解码。将%的字节序列,转换为字符。

7.3.     GET内容编码和和解码

指:URL地址中“?”之后的部分。

       以页面编码字符集,对提交内容进行编码;见“以’UTF-8发送选中,带参数”部分。

       转码:如果调用encodeURI进行转译,在服务器端接收时,需要做相应的解码(详见“对URL地址转码”小节),有两种方式,一 tomcat端设置URI的字符集为UTF-8字符集;二、tomcat默认字符集(ISO-8859-1);客户端进行二次转码;tomcat应用服务器接收时一次解码,在代码中使用URLDecode类进行二次解码。

       编码:如果不对地址转码,直接提交,参数一页面字符集进行编码。

       解码:则以应用服务器(Tomcat)设置的默认字符集进行解析。 equest.setCharacterEncoding()命令无效。

7.4.     Post内容编码和解码

       编码:以页面编码字符集,对提交内容进行编码。

       解码:服务器端根据

       request.setCharacterEncoding("GBK");

命令进行解码。

7.5.     IE“以UTF-8发送”参数

该选项的转换的范围包括:1)在地址栏直接输入的URL地址字符串;2js中提交的URL地址字符串。不包括“?”之后的部分。

URL地址部分(“?”之前的部分)按照UTF-8编码,效果和encodeURI一致。“?”后面的参数部分不做转码。

7.6.     设置Tomcat URIEncoding参数

Tomcat配置文件设置URI解析字符集

对提交的整个URI进行解析(包括参数部分,不包括IP地址和端口)。

8.      URI转码函数

8.1.     encodeURI / decodeURI函数

  1. “转义”定义

    所谓转义,程序用指定的字符构成,当程序处理组成本程序的字符时,或者该字符不可识别时,此时程序会默认将该字符作为程序本身的字符进行处理,而不是作为被处理对象的内容进行处理。对于此种情况,则需要转义,即转义字符后的字符,表示的不是本意,例如我们常见的换行“/n”,“/”表示转义字符,即后面的字符“n”表示的含义已经不是原来26个字母中n的语义,而是表示换行。

    同理在URL%表示后面的为字符编码。

  2. encodeURI()

    转译URI中的字符。

    摘要:encodeURI(uri)

    参数:

            uri 一个字符串,含有uri或者需要编码的文本。

    返回值:

            uri的副本,其中某些字符被十六进制的转译序列替代了。

    抛出:

            说明uri中含有格式化错误的Unicode替代对,不能编码。

    描述:

            encodeURI()是全局函数,返回uri的编码副本。ASCII的字母和数字不编码,此外以下的ASCII标点符号(ASCII Mark)也不编码。

            因为endcodeURI()目的是给URI进行完整的编码,所以URI中有特殊含义的保留字符,也不转义。

            除了上述四种字符(ASCII字母、数字、保留字符、标记字符),uri中的其他字符都将转换成它的UTF-8的编码字符,

     

  3. decodeURI()

     

8.2.     encodeURIComponent / decodeURIComponent

8.3.     两者区别

<以下摘自EMCA-262>

【大意:】对于保留字符“:”,“/”,“;”和“?”encodeURI是不编码的的,encodeURICompoent方法是编码的。

9.      样例

  1. 环境

    jsurl进行编码(调用encodeURI方法)提交。

    IE页面设置的字符集为:GBK

    url地址提交的数据,keyuserid,“火”。

    form表单提交的数据:keyuserid1,“火”。

    即:

    url地址按照UTF-8编码,提交。

    get参数按照UTF-8编码,提交。

    post参数按照GBK编码,提交。

     

    1GET提交数据:

    截获的提交字符串。

    得出,get提交的数据,被转义为UTF-8的十六进制序列。

     

    2)提交post数据

    编码

    根据编码可以得出,POST提交数据是GBK编码。

     

  2. Tomcat 按照默认编码(ISO-8859-1

    找不到。

    结果如下:

  3. Tomcat 按照UTF-8编码

Tomcat按照UTF-8解析URI字节流。

request设置GBK字符集,post数据可识别,设置代码,如下:

结果如下:

如果不显示的设置request编码,出现乱码,如下:

userid的解码字符集,为UTF-8,即tomcatURIEncoding中设置的字符集。

 

  1.  

10.   处理流程

准备:需要提交的URLhttp://localhost/urlencode//urlencode-2.jsp?userid=''

第一步:输入URL地址,获得URL字符串

       包括两种:

  1. 在地址栏直接输入URL字符串。

  2. 调用js方法输入URL字符串。在此方式中,可以对URL字符串进行转码操作(调用encodeURI /encodeURIComponent 方法,将URL规则以外的字符,转换为十六进制的字符序列)得到转换后的字符串。最为最终的URL字符串。如下:

    第二步:IE对提交URL字符串进行编码

           首先查看“以UTF-8发送”是否选中,如果选中,对URL字符串中的地址部分(不包括“?”后的参数),进行转码。未选中,保留字符串不变。

           按照页面编码字符集对URL字符串(整体)进行编码。

    第三步:通过HTT协议提交到服务器端

    第四步:服务器对解码。

           服务器端解码,包括两部分,如下:

    一:应用服务器解析URL

           应用服务器接受到请求的字节序列,按照指定的字符集(Tomcat默认是ISO-8859-1)对字节流解码。注意:遇到“%”,将“%”后的字符按照十六进制字节序列处理,按照指定的字符集转换为相应的字符串。

           注意:如果客户端调用encodeURI方法,对URL字符串进行转换,应用服务器端字符集应设置为UTF-8,和客户端保持一致。如果依旧为默认的ISO-8859-1,则需要应用程序对提交UTF-8字符进行解码,此时需要IE客户端对URL进行二次重复转换(  encodeURI(encodeURI(url))  )。

    二:应用服务器解析提交的内容。

           应用服务器读取request.setCharacterEncoding()方法设置的字符集,对提交的内容进行解码,提交的内容包括:post内容,和get方式的内容。

11.   HTML

<META http-equiv="Content-Type"content="text/html; charset=GBK" />

指定了页面内容的字符集,被绑定到http响应头信息中,如果jsp中也设置了,jsp优先。

12.   jsp字符集

<%@page contentType="text/html;charset=GBK" %>

指令,表示jsp文件以gbk格式的字节流,应答给客户端。

jsp源文件以gbk保存,而在page中,标记为utf-8

例子:

汉字:火。

12.1. 完整的JSP文件

<%@ page pageEncoding="GBK" %>

<%@ page contentType="text/html;charset=GBK" %>

<HTML>

 <HEAD>

  <TITLE> New Document</TITLE>

 </HEAD>

 <BODY></BODY>

</HTML>

 

12.2. GBK保存,contentTypeGBKpageCoding:无

<%@ page contentType="text/html;charset=GBK" %>

文件保存ansi/GBK格式。

IE访问正常。

找到该jsp对应的java文件,“火”字显示正常,如下:

以二进制打开,该字符二进制显示,如下:

(“火”字的UTF-8编码值)

根据字符映射,java文件的保存编码为UTF-8格式。

12.3. GBK保存,contentTypeUTF-8pageCoding:无

jsp文件中,如下:

<%@page contentType="text/html;charset=UTF-8" %>

jsp文件保存GBK格式。部署到Tomcat,通过IE访问。页面如下:

显示乱码。

找到该jsp对应的java文件,“火”字显示乱码,以二进制打开,该字符二进制显示,如下:

该结果,以UTF-8读取GBK字节流结果一致。即以GBK格式转换“火”的字节流保存在磁盘上,应用服务器以utf-8格式解析该字节流。

12.4. GBK保存,contentTypeUTF-8pageCoding GBK

添加头

<%@page  pageEncoding="GBK" %>

<%@page contentType="text/html;charset=UTF-8" %>

jsp文件保存GBK格式。部署到Tomcat,通过IE访问,显示正常,IE显示的编码格式为UTF-8,。

http响应头信息:

找到该jsp对应的java文件,“火”字显示正常,如下: 

以二进制打开,该字符二进制显示,如下:

根据字符映射,java文件的保存编码为UTF-8格式。

 

另:将头改为

<%@page  pageEncoding="GBK" %>

<%@page contentType="text/html;charset=GBK"%>后,显示正常。

HTTP头显示为:

找到该jsp对应的java文件,“火”字显示正常,如下: 

12.5. 步骤

  • JSP文件载入

    • 从硬盘中读取JSP文件,得到该文件的二进制流。

    • 应用服务器根据指定的字符集解码JSP文件。

  • 生成Servlet

    • 调用JSP引擎,生成Servlet文件,根据指定字符集编码,保存到硬盘上。

    • 编译Servlet,生成class文件。

  • 生成应答的输出流

    • 根据指定的字符集,加载该Servletclass文件。

    • 根据Servlet指定的字符集,生成输出流。

  • IE接收并到输出流

    • IE客户端接收字节流

    • 根据指定字符集生成HTML页面

       

12.6. 结论

  1. jsp文件被应用服务器编译为java文件后,java文件以utf-8格式保存。

  2. 应用服务器读取jsp文件,读取的字符集,按照jsppageEncoding指令决定,如下:

    <%@ page pageEncoding="GBK" %>

    如果没有设置该指令,以默认取contentType属性值。

  3. contentType属性中,mimeType指示浏览器显示内容的格式,即用什么应用程序或者字符集显示内容;text/html,设置response的输出字节流,返回客户端的字节流编码格式。

    <%@ page contentType=" text/html;charset=UTF-8" %>

    应用服务器将该指令的内容,绑定到HTTP响应头中,浏览器根据响应头信息解码。

  4.  

13.   java平台字符集

char类型的编码为utf-16的编码格式。由char组成StringString为平台提供操作字符的工具。String是基础,char则是构建基础的元素。

java源文件,由本地操作系统决定。javac在读取源文件时,默认读取本地操作系统的字符集,读取java源文件。

javac编译class文件为unicode字符集,编码为utf-8格式。

jvm运行,字符集为unicode字符集,编码格式utf-16格式。

14.   Tomcat处理内容的默认编码

Tomcat 6.0设置字符集,      URIEncoding="UTF-8"

15.   字符集编码-问题研究(摘自网络)

15.1. 概述

本文主要包括以下几个方面:编码基本知识,java,系统软件,url,工具软件等。

在下面的描述中,将以"中文"两个字为例,经查表可以知道其GB2312编码是"d6d0 cec4"Unicode编码为"4e2d 6587"UTF编码就是"e4b8ad e69687"。注意,这两个字没有iso8859-1编码,但可以用iso8859-1编码来"表示"

15.2. 编码基本知识

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

15.2.1.   iso8859-1

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

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

15.2.2.    GB2312/GBK

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

15.2.3.    unicode

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

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

15.2.4.    UTF

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

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

15.3. java对字符的处理

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

15.3.1.    getBytes(charset)

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

15.3.2.    newString(charset)

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

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

15.3.3.   setCharacterEncoding()

该函数用来设置http请求或者相应的编码。

对于request,是指提交内容的编码,指定后可以通过getParameter()则直接获得正确的字符串,如果不指定,则默认使用iso8859-1编码,需要进一步处理。参见下述"表单输入"。值得注意的是在执行setCharacterEncoding()之前,不能执行任何getParameter()java doc上说明:This method must be called priorto reading request parameters or reading input using getReader()。而且,该指定只对POST方法有效,对GET方法无效。分析原因,应该是在执行第一个getParameter()的时候,java将会按照编码分析所有的提交内容,而后续的getParameter()不再进行分析,所以setCharacterEncoding()无效。而对于GET方法提交表单是,提交的内容在URL中,一开始就已经按照编码分析所有的提交内容,setCharacterEncoding()自然就无效。

对于response,则是指定输出内容的编码,同时,该设置会传递给浏览器,告诉浏览器输出内容所采用的编码。

15.3.4.   处理过程

下面分析两个有代表性的例子,说明java对编码有关问题的处理方法。

15.3.4.1.        表单输入

Userinput  *(gbk:d6d0 cec4)  browser  *(gbk:d6d0cec4)  web server  iso8859-1(00d6 00d 000ce 00c4)  class,需要在class中进行处理:getbytes("iso8859-1")d6 d0 ce c4new String("gbk")d6d0 cec4,内存中以unicode编码则为4e2d 6587

l 用户输入的编码方式和页面指定的编码有关,也和用户的操作系统有关,所以是不确定的,上例以gbk为例。

l browserweb server,可以在表单中指定提交内容时使用的字符集,否则会使用页面指定的编码。而如果在url中直接用?的方式输入参数,则其编码往往是操作系统本身的编码,因为这时和页面无关。上述仍旧以gbk编码为例。

l Web server接收到的是字节流,默认时(getParameter)会以iso8859-1编码处理之,结果是不正确的,所以需要进行处理。但如果预先设置了编码(通过request. setCharacterEncoding ()),则能够直接获取到正确的结果。

l 在页面中指定编码是个好习惯,否则可能失去控制,无法指定正确的编码。

15.3.4.2.        文件编译

假设文件是gbk编码保存的,而编译有两种编码选择:gbk或者iso8859-1,前者是中文windows的默认编码,后者是linux的默认编码,当然也可以在编译时指定编码。

Jsp *(gbk:d6d0 cec4)  java file  *(gbk:d6d0 cec4)  compilerread  uincode(gbk: 4e2d 6587; iso8859-1: 00d6 00d 000ce 00c4)  compilerwrite  utf(gbk: e4b8ad e69687; iso8859-1: *)  compiled file unicode(gbk: 4e2d 6587; iso8859-1: 00d6 00d 000ce 00c4)  class。所以用gbk编码保存,而用iso8859-1编译的结果是不正确的。

class unicode(4e2d 6587)  system.out / jsp.out  gbk(d6d0 cec4) os console / browser

l 文件可以以多种编码方式保存,中文windows下,默认为ansi/gbk

l 编译器读取文件时,需要得到文件的编码,如果未指定,则使用系统默认编码。一般class文件,是以系统默认编码保存的,所以编译不会出问题,但对于jsp文件,如果在中文windows下编辑保存,而部署在英文linux下运行/编译,则会出现问题。所以需要在jsp文件中用pageEncoding指定编码。

l Java编译的时候会转换成统一的unicode编码处理,最后保存的时候再转换为utf编码。

l 当系统输出字符的时候,会按指定编码输出,对于中文windows下,System.out将使用gbk编码,而对于response(浏览器),则使用jsp文件头指定的contentType,或者可以直接为response指定编码。同时,会告诉browser网页的编码。如果未指定,则会使用iso8859-1编码。对于中文,应该为browser指定输出字符串的编码。

l browser显示网页的时候,首先使用response中指定的编码(jsp文件头指定的contentType最终也反映在response上),如果未指定,则会使用网页中meta项指定中的contentType

15.3.5.   几处设置

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

15.3.5.1.        jsp编译

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

15.3.5.2.        jsp输出

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

15.3.5.3.         meta设置

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

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

需要注意的是,apache有一个设置可以给无编码指定的网页指定编码,该指定等同于jsp的编码指定方式,所以会覆盖静态网页中的meta指定。所以有人建议关闭该设置。

15.3.5.4.         form设置

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

15.4. 系统软件

下面讨论几个相关的系统软件。

15.4.1.    mysql数据库

很明显,要支持多语言,应该将数据库的编码设置成utf或者unicode,而utf更适合与存储。但是,如果中文数据中包含的英文字母很少,其实unicode更为适合。

数据库的编码可以通过mysql的配置文件设置,例如default-character-set=utf8。还可以在数据库链接URL中设置,例如: useUnicode=true&characterEncoding=UTF-8。注意这两者应该保持一致,在新的sql版本里,在数据库链接URL里可以不进行设置,但也不能是错误的设置。

15.4.2.    apache

appache和编码有关的配置在httpd.conf中,例如AddDefaultCharset UTF-8。如前所述,该功能会将所有静态页面的编码设置为UTF-8,最好关闭该功能。

另外,apache还有单独的模块来处理网页响应头,其中也可能对编码进行设置。

15.4.3.    linux默认编码

这里所说的linux默认编码,是指运行时的环境变量。两个重要的环境变量是LC_ALLLANG,默认编码会影响到java URLEncode的行为,下面有描述。

建议都设置为"zh_CN.UTF-8"

15.4.4.   其它

为了支持中文文件名,linux在加载磁盘时应该指定字符集,例如:mount /dev/hda5 /mnt/hda5/ -t ntfs -o iocharset=gb2312

另外,如前所述,使用GET方法提交的信息不支持request.setCharacterEncoding(),但可以通过tomcat的配置文件指定字符集,在tomcatserver.xml文件中,形如:<Connector ...URIEncoding="GBK"/>。这种方法将统一设置所有请求,而不能针对具体页面进行设置,也不一定和browser使用的编码相同,所以有时候并不是所期望的。

15.5. URL地址

URL地址中含有中文字符是很麻烦的,前面描述过使用GET方法提交表单的情况,使用GET方法时,参数就是包含在URL中。

15.5.1.   URL编码

对于URL中的一些特殊字符,浏览器会自动进行编码。这些字符除了"/?&"等外,还包括unicode字符,比如汉子。这时的编码比较特殊。

IE有一个选项"总是使用UTF-8发送URL",当该选项有效时,IE将会对特殊字符进行UTF-8编码,同时进行URL编码。如果改选项无效,则使用默认编码"GBK",并且不进行URL编码。但是,对于URL后面的参数,则总是不进行编码,相当于UTF-8选项无效。比如"中文.html?a=中文",当UTF-8选项有效时,将发送链接"%e4%b8%ad%e6%96%87.html?a=\x4e\x2d\x65\x87";而UTF-8选项无效时,将发送链接"\x4e\x2d\x65\x87.html?a=\x4e\x2d\x65\x87"。注意后者前面的"中文"两个字只有4个字节,而前者却有18个字节,这主要时URL编码的原因。

web servertomcat)接收到该链接时,将会进行URL解码,即去掉"%",同时按照ISO8859-1编码(上面已经描述,可以使用URLEncoding来设置成其它编码)识别。上述例子的结果分别是"\ue4\ub8\uad\ue6\u96\u87.html?a=\u4e\u2d\u65\u87""\u4e\u2d\u65\u87.html?a=\u4e\u2d\u65\u87",注意前者前面的"中文"两个字恢复成了6个字符。这里用"\u",表示是unicode

所以,由于客户端设置的不同,相同的链接,在服务器上得到了不同结果。这个问题不少人都遇到,却没有很好的解决办法。所以有的网站会建议用户尝试关闭UTF-8选项。不过,下面会描述一个更好的处理办法。

15.5.2.    rewrite

熟悉的人都知道,apache有一个功能强大的rewrite模块,这里不描述其功能。需要说明的是该模块会自动将URL解码(去除%),即完成上述web servertomcat)的部分功能。有相关文档介绍说可以使用[NE]参数来关闭该功能,但我试验并未成功,可能是因为版本(我使用的是apache 2.0.54)问题。另外,当参数中含有"?& "等符号的时候,该功能将导致系统得不到正常结果。

rewrite本身似乎完全是采用字节处理的方式,而不考虑字符串的编码,所以不会带来编码问题。

15.5.3.    URLEncode.encode()

这是Java本身提供对的URL编码函数,完成的工作和上述UTF-8选项有效时浏览器所做的工作相似。值得说明的是,java已经不赞成不指定编码来使用该方法(deprecated)。应该在使用的时候增加编码指定。

当不指定编码的时候,该方法使用系统默认编码,这会导致软件运行结果得不确定。比如对于"中文",当系统默认编码为"gb2312"时,结果是"%4e%2d%65%87",而默认编码为"UTF-8",结果却是"%e4%b8%ad%e6%96%87",后续程序将难以处理。另外,这儿说的系统默认编码是由运行tomcat时的环境变量LC_ALLLANG等决定的,曾经出现过tomcat重启后就出现乱码的问题,最后才郁闷的发现是因为修改修改了这两个环境变量。

建议统一指定为"UTF-8"编码,可能需要修改相应的程序。

15.5.4.   一个解决方案

上面说起过,因为浏览器设置的不同,对于同一个链接,web server收到的是不同内容,而软件系统有无法知道这中间的区别,所以这一协议目前还存在缺陷。

针对具体问题,不应该侥幸认为所有客户的IE设置都是UTF-8有效的,也不应该粗暴的建议用户修改IE设置,要知道,用户不可能去记住每一个web server的设置。所以,接下来的解决办法就只能是让自己的程序多一点智能:根据内容来分析编码是否UTF-8

比较幸运的是UTF-8编码相当有规律,所以可以通过分析传输过来的链接内容,来判断是否是正确的UTF-8字符,如果是,则以UTF-8处理之,如果不是,则使用客户默认编码(比如"GBK"),下面是一个判断是否UTF-8的例子,如果你了解相应规律,就容易理解。

publicstatic boolean isValidUtf8(byte[] b,int aMaxCount){

      int lLen=b.length,lCharCount=0;

      for(int i=0;i<lLen && lCharCount<aMaxCount;++lCharCount){

             byte lByte=b[i++];//to fast operation, ++ now, ready for the following for(;;)

             if(lByte>=0) continue;//>=0 is normal ascii

             if(lByte<(byte)0xc0 || lByte>(byte)0xfd) return false;

             int lCount=lByte>(byte)0xfc?5:lByte>(byte)0xf8?4

                    :lByte>(byte)0xf0?3:lByte>(byte)0xe0?2:1;

             if(i+lCount>lLen) return false;

             for(int j=0;j<lCount;++j,++i) if(b[i]>=(byte)0xc0) return false;

      }

      return true;

}

相应地,一个使用上述方法的例子如下:

publicstatic String getUrlParam(String aStr,String aDefaultCharset)

throwsUnsupportedEncodingException{

      if(aStr==null) return null;

      byte[] lBytes=aStr.getBytes("ISO-8859-1");

      return new String(lBytes,StringUtil.isValidUtf8(lBytes)?"utf8":aDefaultCharset);

}

不过,该方法也存在缺陷,如下两方面:

l 没有包括对用户默认编码的识别,这可以根据请求信息的语言来判断,但不一定正确,因为我们有时候也会输入一些韩文,或者其他文字。

l 可能会错误判断UTF-8字符,一个例子是"学习"两个字,其GBK编码是" \xd1\xa7\xcf\xb0",如果使用上述isValidUtf8方法判断,将返回true。可以考虑使用更严格的判断方法,不过估计效果不大。

有一个例子可以证明google也遇到了上述问题,而且也采用了和上述相似的处理方法,比如,如果在地址栏中输入"http://www.google.com/search?hl=zh-CN&newwindow=1&q=学习"google将无法正确识别,而其他汉字一般能够正常识别。

最后,应该补充说明一下,如果不使用rewrite规则,或者通过表单提交数据,其实并不一定会遇到上述问题,因为这时可以在提交数据时指定希望的编码。另外,中文文件名确实会带来问题,应该谨慎使用。

15.6. 其它

下面描述一些和编码有关的其他问题。

15.6.1.    SecureCRT

除了浏览器和控制台与编码有关外,一些客户端也很有关系。比如在使用SecureCRT连接linux时,应该让SecureCRT的显示编码(不同的session,可以有不同的编码设置)和linux的编码环境变量保持一致。否则看到的一些帮助信息,就可能是乱码。

另外,mysql有自己的编码设置,也应该保持和SecureCRT的显示编码一致。否则通过SecureCRT执行sql语句的时候,可能无法处理中文字符,查询结果也会出现乱码。

对于Utf-8文件,很多编辑器(比如记事本)会在文件开头增加三个不可见的标志字节,如果作为mysql的输入文件,则必须要去掉这三个字符。(用linuxvi保存可以去掉这三个字符)。一个有趣的现象是,在中文windows下,创建一个新txt文件,用记事本打开,输入"连通"两个字,保存,再打开,你会发现两个字没了,只留下一个小黑点。

15.6.2.   过滤器

如果需要统一设置编码,则通过filter进行设置是个不错的选择。在filterclass中,可以统一为需要的请求或者回应设置编码。参加上述setCharacterEncoding()。这个类apache已经给出了可以直接使用的例子SetCharacterEncodingFilter

15.6.3.    POSTGET

很明显,以POST提交信息时,URL有更好的可读性,而且可以方便的使用setCharacterEncoding()来处理字符集问题。但GET方法形成的URL能够更容易表达网页的实际内容,也能够用于收藏。

从统一的角度考虑问题,建议采用GET方法,这要求在程序中获得参数是进行特殊处理,而无法使用setCharacterEncoding()的便利,如果不考虑rewrite,就不存在IEUTF-8问题,可以考虑通过设置URIEncoding来方便获取URL中的参数。

15.6.4.   简繁体编码转换

GBK同时包含简体和繁体编码,也就是说同一个字,由于编码不同,在GBK编码下属于两个字。有时候,为了正确取得完整的结果,应该将繁体和简体进行统一。可以考虑将UTFGBK中的所有繁体字,转换为相应的简体字,BIG5编码的数据,也应该转化成相应的简体字。当然,仍旧以UTF编码存储。

例如,对于"语言 語言",用UTF表示为"\xE8\xAF\xAD\xE8\xA8\x80\xE8\xAA\x9E\xE8\xA8\x80",进行简繁体编码转换后应该是两个相同的 "\xE8\xAF\xAD\xE8\xA8\x80>"

 

Manufacturer.com刘科垠

2006-3-8

 

你可能感兴趣的:(http,中文乱码,字符集,java乱码)