在Java Web中的中文编码问题

      Java是一门跨平台语言,在不同平台上涉及到很多编码问题,所以需要明白在Java中出现编码问题的根本原因。需要明白在Java中经常遇到几种编码格式的区别;在Java中需要使用编码的场景;出现中文问题的原因;在开发Java Web程序时可能会存在编码的几个地方;一个HTTP请求怎么控制编码格式;以及如何避免中文编码问题。

    先思考为什么要编码?

      计算机中存储信息的最小单元是1个字节,也就是8bit,所能表示的字符范围是0~255个。而人类的语言太多,表示这些语言的符号太多,导致无法用计算机中基本的存储单元来表示,因而必须要经过拆分或一些翻译工作。所以要进行编码。

     编码格式即为翻译方式,常见的又ASCII、ISO-8859-1、GB2312、GBK、UTF-8、UTF-16等,以上六种中,后四种都可以用来表示汉字,对于那种编码方式的选择,要看其他的因素,究竟是存储空间重要还是编码效率重要。  

    ASCII一共能表示128个字符,ISO-8859-1相当于ASCII的扩充,但ISO-8859-1仍是单字节字符,总共能表示256个字符。GB2312是双字节编码,能表示汉字,包含6763个汉字,GBK相当于GB2312的扩展,能表示21003个汉字,并且它是对GB2312兼容的。UTF-16用两个字节来表示Unicode的转化方式,采用定长的表示方法,即无论什么字符,均用两个字节来表示,也正是因为这一点,大大简化了字符串的操作,编码效率很高,这也是Java以UTF-16作为内存的字符存储格式的一个重要原因。UTF-16的虽然简单,方便,但是大部分字符用一个字节就可以表示,UTF-16导致存储空间扩大了一倍,特别是在现在网络带宽还比较有限的情况下,会增大网络传输的流量。而UTF-8采用了变长技术,对于每个编码区有不同的字码长度。

    再来看一下在Java中需要编码的场景,看一下在什么场合中需要编码,需要编码的场合主要是在I/O操作中和在内存操作中。

   涉及到编码的地方一般都是涉及到字节与字符的转换上,而需要转换的场景主要是I/O,包括磁盘I/O和网络I/O。以下是Java处理I/O问题的接口:

    在应用程序中涉及到I/O操作时,只要注意指定统一的编解码Charset字符集,一般都不会出问题。如果不注意指定字符编码,则在中文环境中会用操作系统的默认编码,容易出问题。

    在Java开发中除I/O涉及到编码外,最常用的就是在内存中进行从字符到字节的数据类型转换,而在String中提供了字节与字符之间互相转换的方法,但需要注意的是,转换的时候,也需要注意编码格式的统一。

      再来看下在Java中是如何编解码的,,首先会根据指定的charsetName通过反射找到相应的Charset类,根据Charset创建CharsetEncode对象,再调用CharsetEncode.encode方法对字符串进行编码,不同的编码都会对应到一个类中,实际的编码过程也是在这些类中完成的。来看下时序图:

      对于中文字符,虽然好几种编码格式都能处理,但是各有各的特点。UTF-16编码效率较高,从字符到字节的相互转换更简单,进行字符串操作操作也更好,适合在磁盘到内存之间使用,可进行字符到字节的快速切换,但不适合在网络之间传输,因为网络传输容易损坏字节流,而一旦字节流损坏将很难恢复,所以UTF-8更适合网络传输。UTF-8对ASCII字符采用单字节存储,另外单个字符损坏也不会影响后面的其他字符,在编码效率上介于GBK与UTF-16之间,所以UTF-8在编码效率上和编码安全性上做了平衡。

      从使用中文的角度来看,有I/O的地方就会涉及到编码,而大部分引起乱码的I/O都是网络I/O,数据在网络中传输都是以字节为单位,所以所有的数据都必须能够被序列化字节,在Java中,必须继承Serializable接口。现在来看看在Java Web中涉及到的编解码

     下图大致介绍了一下一个HTTP请求需要编解码的流程:

    一个HTTP请求在很多地方需要编解码,下面来看下它们编解码的规则是什么。

    URL的编解码

     用户提交的URL,其中可能存在中文,因此需要编码,来看下Servlet中URL的结构:

 

图中因为是直接在浏览器里输入的URL,所以是通过GET方法请求的,如果是通过POST方法请求的,QueryString将通过表单方式提交到服务器端。图中PathInfo和QueryString部分出现了中文,当我们在浏览器中输入这个URL时,看下浏览器端和服务端会如何编码和解析这个URL。选择Firefox浏览器并通过HTTPFox插件观察到请求的URL的实际如下:

  可以看出虽然同为“君山”,但是其编码格式是不一样的,PathInfo是UTF-8编码,QueryString是GBK编码,有%是因为浏览器编码URL将非ASCII字符按照某种编码格式编码成16进制数字后将每个16进制表示的字节前加上%。

      从上面的测试结果可以看出,浏览器对PathInfo和QueryString的编码是不一样的,不同的浏览器对PathInfo的编码可能也不一样,这就为服务器的解码带来了很大困难。

     下面以Tomcat服务器为例,介绍对QueryString的解析过程,以GET方式HTTP请求的QueryString与以POST方式HTTP请求的表单参数都是以Paramemters保存的,,都通request.getParamemter获取参数值。对他们的解码是在request.getParamemter第一次被调用时进行的。先来看下以GET方式传递的参数,QueryString的解码字符集是在哪里定义的,它本身是通过HTTP的Header传到服务端的,并且也在URL中,从前面的讲解中可以知道,它和URI的解码字符集不是一样的。QueryString的解码字符集要么是Header中ContentType中定义的CharSet,要么是默认的ISO-8859-1,要实用ContentType中定义的编码,就要将connector的<Connector URIEncoding="UTF-8" useBodyEncodingForURI="true"/>中的useBodyEncodingForURI设置为true。但是这个配置项并不是对整个URI都采用BodyEncoding进行解码,而仅仅是对QueryString实用BodyEncoding解码。从上面的URL编码和解码过程来看,比较复杂,赢尽量避免在URL中使用非ASCII字符。当然,最好在服务端设置<Connector/>中的URIEncoding和useBodyEncodingForURI两个参数。

   HTTP Header的编解码

      当客户端发起HTTP请求时,除上面的URL外还可能会在Header中传递其他参数,如Cookie、redirectPath等,这些用户设置的值很可能会存在编码问题,还是以Tomcat为例,看看是如何对其进行解码的。对Header中的项进行解码是在调用request.getHeader时进行的。如果请求的Header项没有解码则调用MessageBytes的toString方法,这个方法对从byte到char的转化使用的默认编码也是ISO-8859-1,而且不能设置Header的其他编码格式,所以如果你设置的Header中有非ASCII字符,解码中肯定会出现乱码。在添加Header时也要注意,不要在Header中传递非ASCII字符,如果一定要传递,可以先对这些字符进行编码,再添加到Header中,这样就不会丢失信息,要访问这些项时再进行解码即可。

   POST表单的编解码

     POST表单提交的参数也是在第一次调用request.getParamemter时发生的,POST表单的参数传递方式与QueryString不同,它是通过BODY传递到服务端的。当我们在页面上单击提交按钮时浏览器首先将根据ContentType的Charset编码格式对在表单中填入的参数进行编码,然后提交到服务器端,在服务器端同样也是用ContentType中的字符集进行解码。所以通过POST表单提交的参数一般不会出现问题,而且这个字符集编码是我们自己设置的,可以通过request.setCharacterEncoding(charset)来设置,但要注意的是一定要在第一次调用request.getParamemter之前就设置request.setCharacterEncoding(charset),因为服务器会在第一次调用request.getParamemter时对参数集合进行解码。如果不设置编码格式,将会使用磨人的编码方式进行解码,在Tomcat中是ISO-8859-1。

      另外,对于上传的文件编码,同样是使用ContentType定义的字符集编码,但是上传文件时候用字节流的方式传输到服务器本地临时目录中,这个过程并没有涉及到字符编码,为真正编码是在将文件内容添加到paramemters中时,如果用这个不能编码,则将会使用默认编码ISO-8859-1来编码。

HTTP BODY的编解码

      当用户请求的资源成功获取后,这些内容将通过Response返回给客户端浏览器,这个过程要先经过编码,再到浏览器进行解码。编解码字符集可以通过response.setCharacterEncoding来设置,它会覆盖request.setCharacterEncoding的值,并且通过Heaeder的Content-Type返回客户端,浏览器接收到返回的Socket流时将通过Content-Type的Charset来解码。如果没有设置,则根据HTML中设置的Charset来解码,如果也没有定义,那么浏览器将使用默认的编码来解码。

 

 

   出现乱码问题是因为在char到byte或从byte到char的转换中编码和解码的字符集不一致导致的。但是一次操作涉及多次编解码,多排查问题时会比较难。

 

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值