Web服务系统中文编码和解码问题解决方法

Web服务系统是分布式的,需要多个处理节点配合,而且很多节点是不可控的,例如,用户的浏览器,因此,中文的编码和解码十分麻烦,必须要每个环节都考虑周全,下面是本人在实践中逐步总结出来的,欢迎批评指正。

JSP页面显示乱码

例如,下面的JSP页面显示时可能会出现乱码:

<html>
<head>
<title>JSP的中文处理</title>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
</head>
<body>
<% out.print("JSP的中文处理"); %>
</body>
</html>

原因分析:对不同的WEB服务器和不同的JDK版本,处理结果就不一样。原因:服务器使用的编码方式不同和浏览器对不同的字符显示结果不同而导致的。
解决办法:在 JSP页面中指定编码方式(gb2312),即在页面的第一行加上:<%@ page contentType="text/html; charset=gb2312"%>,就可以消除乱码了。完整页面如下:

<%@ page contentType="text/html; charset=gb2312"%>
<html>
<head>
<title>JSP的中文处理</title>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
</head>
<body>
<% out.print("JSP的中文处理"); %>
</body>
</html>


Form表单提交中文时出现乱码

下面是一个提交页面(submit.jsp),代码如下:

<html>
<head>
<title>JSP的中文处理</title>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
</head>
<body>
<form name="form1" method="post" action="process.jsp">
<input type="text" name="name">
<input type="submit" name="Submit" value="Submit">
</form>
</body>
</html>

下面是处理页面(process.jsp)代码:

<%@ page contentType="text/html; charset=gb2312"%>
<html>
<head>
<title>JSP的中文处理</title>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
</head>
<body>
<%=request.getParameter("name")%>
</body>
</html>

如果submit.jsp提交英文字符能正确显示,如果提交中文时就会出现乱码。原因:浏览器默认使用UTF-8编码方式来发送请求,而UTF-8和 GB2312编码方式表示字符时不一样,这样就出现了不能识别字符。解决办法:通过request.setCharacterEncoding ("gb2312")对请求进行统一编码,就实现了中文的正常显示。修改后的process.jsp代码如下:

<%@ page contentType="text/html; charset=gb2312"%>
<% request.seCharacterEncoding("gb2312"); %>
<html>
<head>
<title>JSP的中文处理</title>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
</head>
<body>
<%=request.getParameter("name")%>
</body>
</html>


Spring架构中JSP出现乱码

在Spring MVC架构中,通过View将JSP页面呈现给客户端,即使象上节那样使用page指令设置了编码,但是如果使用 SimpleFormController处理提交的Form时,发现request.getCharacterEncoding()取不到编码,根据 HTML规范,form提交时的编码跟下载含有form的HTML页面时的HTTP contentType一致,所以,断定Spring在呈现JSP时,没有处理page指令。

有多种解决方法:

  • 参照[1]: 采用了重置isFormSubmission()的方法,可以有效解决这个问题。另外,在Form元素中声明accept-charset属性。
  • 参照[2]: 如果没有声明contentType,servlet缺省按照ISO-8859-1进行解码:
    The default request encoding according to the Servlet specification is ISO-8859-1. If the client doesn't send any charset information (and none of the major browsers do) then this is used, unless you explicitly set a different encoding, for example in a filter. Most browsers respond with the same charset your response was in, so what I'm doing at the moment is to send *only* UTF-8 in my responses, forcing the request into UTF-8 with a filter, and additionally using accept-charset. It seems to work so far.


Ajax涉及到的浏览器和服务器通信

在此,我们再进一步总结一下浏览器和服务器之间的通信的编码问题,我们假设浏览器使用Firefox,其缺省编码是UTF-8,服务器运行在Windows操作系统上,该操作系统中文版的缺省编码是GBK,如果我们使用Java开发servlet,而使用Javascript开发基于Ajax架构的客户端,在客户端和服务器之间使用HTTP传递XML或者XHTML文档,为了确保上下行中文的正确编码和解码,需要考虑:1,声明XML文档的编码,在其prolog行加上encoding=gbk;2,强制通信层采用特定的编码,在客户端使用Javascript函数;在服务器侧使用HTTPServletResponse.setContentType()方法,设置HTTP 头参数: content-type。只要两端能够取得一致即可,即:1,应用层关于怎样解析XML文档取得一致;2,通信层使用什么传输编码取得一致。



数据库编码

例如使用MySQL数据库,因为MySQL数据库管理系统是一个独立的应用,与自己写的应用程序通信时也要考虑通信编码,同样,数据库存储的内容的编码也要考虑,这同上一章讲的Ajax问题是一样的。如果采用一些开发框架,例如, Hibernate等,自己就可以避免做底层的编码处理。



Java程序处理文件

如果程序要生成一些文件,然后与另一个程序共享,需要协调两者的编码

Java系统内的文本编码

Java系统内,文本采用UTF-16编码,从一个字符串中取出的char类型的编码单元都是两个字节长度的。因为UTF-16编码是变长的,对于BMP页面的编码(即0x0000-0xffff)采用两个字节,所以取出的char类型的字节就是其编码点(code point),而对于补充编码区的编码(0x10000-0x10ffff)采用两个代理字符(surrogate character)表示。

从Java API中类Character的Java doc截取下来的: Unicode Character Representations:

The char data type (and therefore the value that a Character object encapsulates) are based on the original Unicode specification, which defined characters as fixed-width 16-bit entities. The Unicode standard has since been changed to allow for characters whose representation requires more than 16 bits. The range of legal code points is now U+0000 to U+10FFFF, known as Unicode scalar value. (Refer to the definition of the U+n notation in the Unicode standard.)

The set of characters from U+0000 to U+FFFF is sometimes referred to as the Basic Multilingual Plane (BMP). Characters whose code points are greater than U+FFFF are called supplementary characters. The Java 2 platform uses the UTF-16 representation in char arrays and in the String and StringBuffer classes. In this representation, supplementary characters are represented as a pair of char values, the first from the high-surrogates range, (\uD800-\uDBFF), the second from the low-surrogates range (\uDC00-\uDFFF).

A char value, therefore, represents Basic Multilingual Plane (BMP) code points, including the surrogate code points, or code units of the UTF-16 encoding. An int value represents all Unicode code points, including supplementary code points. The lower (least significant) 21 bits of int are used to represent Unicode code points and the upper (most significant) 11 bits must be zero. Unless otherwise specified, the behavior with respect to supplementary characters and surrogate char values is as follows:

  • The methods that only accept a char value cannot support supplementary characters. They treat char values from the surrogate ranges as undefined characters. For example, Character.isLetter('\uD840') returns false, even though this specific value if followed by any low-surrogate value in a string would represent a letter.
  • The methods that accept an int value support all Unicode characters, including supplementary characters. For example, Character.isLetter(0x2F81A) returns true because the code point value represents a letter (a CJK ideograph).

In the J2SE API documentation, Unicode code point is used for character values in the range between U+0000 and U+10FFFF, and Unicode code unit is used for 16-bit char values that are code units of the UTF-16 encoding. For more information on Unicode terminology, refer to the Unicode Glossary.

文件存取

如果文本都使用UTF-8编码,根据上述Java虚拟机使用UTF-16编码,文本内容写文件时,不能使用FileWriter类,而是使用UTF-8编码器将内存中文本编码成UTF-8的字节流,使用流操作存储到文件中。如果写XML文件,而且XML文件本身规定charset=UTF -8,如果没有做正确的编码而直接写磁盘,存的XML文件内容会与文件自己的规定不一致,如果将文件在Linux和Window间拷备,则无法保证可交换性。因此一定要使用编码器。

将磁盘文件读入程序时,如果要将文件内容当成文本进行处理,一定要先使用UTF-8解码器解码后再使用,如果将文件内容通过HTTP发送,而且HTTP的通道编码是UTF-8,那么直接字节拷贝即可。

使用Hibernate连接MySQL数据库时处理中文的方法

假设使用UTF-8编码,在Hibernate的属性文件hibernate.properties中应该如下设置:

hibernate.connection.url=jdbc:mysql://地址/数据库名?useUnicode=true&characterEncoding=utf-8
hibernate.connection.useUnicode=true
hibernate.connection.characterEncoding=utf-8
经过测试,上述方法有效,但是,我感觉到后面两句与第一句重复,删除后不能工作。

URL中有中文参数的处理方法

假设编程环境:使用JSP设计页面,JSP动态产生的URL中有中文参数,系统全程使用UTF-8编码

为了能够正确处理中文,主要做以下步骤:

  1. 在服务器的servlet中,首先使用URLEncoder对URL进行 UTF-8编码,然后由JSP展现
  2. Tomcat服务器的Connector要做正确的配置,在Connector标签中,增加属性:URIEncoding="UTF-8"
  3. 服务器上接收URL参数的servlet在处理参数前要使用URLDecoder进行解码。

经过测试,上述方法有效。