Java乱码解决之道

1.常见字符编码
ASCII编码:

ASCII,American Standard Code for Information Interchange,是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其余西欧语言。它是现今最通用的单字节编码系统。javascript

 
ASCII码使用指定的7位或者8为二进制数字组合表示128或者256种可能的字符。标准的ASCII编码使用的是7(2^7 = 128)位二进制数来表示全部的大小写字母、数字和标点符号已经一些特殊的控制字符,最前面的一位统一规定为0。其中0~31及127(共33个)是控制 字符或通讯专用字符,32~126(共95个)是字符(32是空格),其中48~57为0到9十个阿拉伯数字,65~90为26个大写英文字 母,97~122号为26个小写英文字母,其他为一些标点符号、运算符号等。
 
GBK***编码:

ASCII最大的缺点就是显示字符有限,他虽然解决了部分西欧语言的显示问题,可是对更多的其余语言他实在是无能为了。随着计算机技术的发展,使用 范围愈来愈普遍了,ASCII的缺陷愈来愈明显了,其余国家和地区须要使用计算机,必需要设计一套符合本国/本地区的编码规则。例如为了显示中文,咱们就 必需要设计一套编码规则用于将汉字转换为计算机能够接受的数字系统的数。html

GB2312,用于汉字处理、 汉字通讯等系统之间的信息交换,通行于中国大陆。它的编码规则是:小于127的字符的意义与原来相同,但两个大于127的字符连在一块儿时,就表示一个汉 字,前面的一个字节(他称之为高字节)从0xA1用到 0xF7,后面一个字节(低字节)从0xA1到0xFE,这样咱们就能够组合出大约7000多个简体汉字了。虽然GB2312收录了这么多汉子,他所覆盖 的使用率能够达到99%,可是对于那些不常见的汉字,例如人名、地名、古汉语,它就不能处理了,因而就有下面的GBK、GB 18030的出现。(点击GB2312简体中文编码表查看)。java

GB18030全 称:国家标准GB 18030-2005《信息技术 中文编码字符集》,是我国计算机系统必须遵循的基础性标准之一,GB18030有两个版本:GB18030-2000和GB18030-2005。 GB18030-2000是GBK的取代版本,它的主要特色是在GBK基础上增长了CJK统一汉字扩充A的汉字。程序员

GB 18030主要有如下特色:web

    与UTF-8相同,采用多字节编码,每一个字能够由1个、2个或4个字节组成。chrome

    编码空间庞大,最多可定义161万个字符。数据库

    支持中国国内少数民族的文字,不须要动用造字区。apache

 
    汉字收录范围包含繁体汉字以及日韩汉字。

GBK,汉字编码标准之一,全称《汉字内码扩展规范》,它 向下与 GB 2312 编码兼容,向上支持 ISO 10646.1 国际标准,是前者向后者过渡过程当中的一个承上启下的标准。它的编码范围以下图:浏览器

 
Unicode编码:

正如前面前面所提到的同样,世界存在这么多国家,也存在着多种编码风格,像中文的GB23二、GBK、GB18030,这样乱搞一套,虽然在本地运行没有问题,可是一旦出如今网络上,因为互不兼容,访问则会出现乱码。为了解决这个问题,伟大的Unicode编码腾空出世。tomcat

Unicode编码的做用就是可以使计算机实现夸平台、跨语言的文本转换和处理。它几乎包含了世界上全部的符号,而且每一个符号都是独一无二的。在它的编码世界里,每个数字表明一个符号,每个符号表明了一个数字,不存在二义性。

 
Unicode编码又称统一码、万国码、单一码,它是业界的一种标准,是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每一个字符设定 了统一而且惟一的二进制编码,以知足跨语言、跨平台进行文本转换、处理的要求。同时Unicode是字符集,它存在不少几种实现方式如:UTF-八、 UTF-16.
UTF-8:

互联网的普及,强烈要求出现一种统一的编码方式。UTF-8就是在互联网上使用最广的一种unicode的实现方式。其余实现方式还包括UTF-16和UTF-32,不过在互联网上基本不用。重复一遍:UTF-8是Unicode的实现方式之一。

 
UTF-8最大的一个特色,就是它是一种变长的编码方式。它可使用1~4个字节表示一个符号,根据不一样的符号而变化字节长度。
UTF-8的编码规则很简单,只有两条:
1)对于单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码。所以对于英语字母,UTF-8编码和ASCII码是相同的。
2)对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一概设为10。剩下的没有说起的二进制位,所有为这个符号的unicode码。
 
 
编码&&编码格式:
首先先看看java编码类图 [1]
 
首先根据指定的chart设置ChartSet类,而后根据ChartSet建立ChartSetEncoder对象,最后再调用 CharsetEncoder.encode 对字符串进行编码,不一样的编码类型都会对应到一个类中,实际的编码过程是在这些类中完成的。下面时序图展现详细的编码过程:
 
经过这编码的类图和时序图能够了解编码的详细过程。下面将经过一段简单的代码对ISO-8859-一、GBK、UTF-8编码:
public class Test02 {
    public static void main(String[] args) throws UnsupportedEncodingException {
        String string = "我是 cm";
        Test02.printChart(string.toCharArray());
        Test02.printChart(string.getBytes("ISO-8859-1"));
        Test02.printChart(string.getBytes("GBK"));
        Test02.printChart(string.getBytes("UTF-8"));
    }
   
    /**
     * char转换为16进制
     */
    public static void printChart(char[] chars){
        for(int i = 0 ; i < chars.length ; i++){
            System.out.print(Integer.toHexString(chars[i]) + " "); 
        }
        System.out.println("");
    }
   
    /**
     * byte转换为16进制
     */
    public static void printChart(byte[] bytes){
        for(int i = 0 ; i < bytes.length ; i++){
            String hex = Integer.toHexString(bytes[i] & 0xFF); 
             if (hex.length() == 1) { 
               hex = '0' + hex; 
             } 
             System.out.print(hex.toUpperCase() + " "); 
        }
        System.out.println("");
    }
}
-------------------------outPut:
6211 662f 20 63 6d 
3F 3F 20 63 6D 
CE D2 CA C7 20 63 6D 
E6 88 91 E6 98 AF 20 63 6D
 

经过程序咱们能够看到“我是 cm”的结果为:

char[]:6211 662f 20 63 6d

ISO-8859-1:3F 3F 20 63 6D
GBK:CE D2 CA C7 20 63 6D
UTF-8:E6 88 91 E6 98 AF 20 63 6D

 
图以下:
 
 
 
 
JAVAWEB中的编码&&解码:
 
 

客户端想服务器发送请求无非就经过四中状况:

一、URL方式直接访问。

二、页面连接。

三、表单get提交

 
四、表单post提交
 

能够看到各大浏览器对“我是”的编码状况以下:

 

path部分

Query String

Firefox

E6 88 91 E6 98 AF

E6 88 91 E6 98 AF

Chrome

E6 88 91 E6 98 AF

E6 88 91 E6 98 AF

IE

E6 88 91 E6 98 AF

CE D2 CA C7

查阅上篇博客的编码可知对于path部分Firefox、chrome、IE都是采用UTF-8编码格式,对于Query String部分Firefox、chrome采用UTF-8,IE采用GBK。至于为何会加上%,这是由于URL的编码规范规定浏览器将ASCII字 符非 ASCII 字符按照某种编码格式编码成 16 进制数字而后将每一个 16 进制表示的字节前加上“%”。

固然对于不一样的浏览器,相同浏览器不一样版本,不一样的操做系统等环境都会致使编码结果不一样,上表某一种状况,对于URL编码规则下任何结论都是过早 的。因为各大浏览器、各个操做系统对URL的URI、QueryString编码均可能存在不一样,这样对服务器的解码势必会形成很大的困扰,下面咱们将已 tomcat,看tomcat是如何对URL进行解码操做的。

解析请求的 URL 是在 org.apache.coyote.HTTP11.InternalInputBuffer 的 parseRequestLine 方法中,这个方法把传过来的 URL 的 byte[] 设置到 org.apache.coyote.Request 的相应的属性中。这里的 URL 仍然是 byte 格式,转成 char 是在 org.apache.catalina.connector.CoyoteAdapter 的 convertURI 方法中完成的:

protected void convertURI(MessageBytes uri, Request request) throws Exception { ByteChunk bc = uri.getByteChunk(); int length = bc.getLength(); CharChunk cc = uri.getCharChunk(); cc.allocate(length, -1); String enc = connector.getURIEncoding(); //获取URI解码集 if (enc != null) { B2CConverter conv = request.getURIConverter(); try { if (conv == null) { conv = new B2CConverter(enc); request.setURIConverter(conv); } } catch (IOException e) {...} if (conv != null) { try { conv.convert(bc, cc, cc.getBuffer().length - cc.getEnd()); uri.setChars(cc.getBuffer(), cc.getStart(), cc.getLength()); return; } catch (IOException e) {...} } } // Default encoding: fast conversion byte[] bbuf = bc.getBuffer(); char[] cbuf = cc.getBuffer(); int start = bc.getStart(); for (int i = 0; i < length; i++) { cbuf[i] = (char) (bbuf[i + start] & 0xff); } uri.setChars(cbuf, 0, length); }

从上面的代码可知,对URI的解码操做是首先获取Connector的解码集,该配置在server.xml中

<Connector URIEncoding="utf-8" />

若是没有定义则会采用默认编码ISO-8859-1来解析。

对于Query String部分,咱们知道不管咱们是经过get方式仍是POST方式提交,全部的参数都是保存在Parameters,而后咱们经过 request.getParameter,解码工做就是在第一次调用getParameter方法时进行的。在getParameter方法内部它调用 org.apache.catalina.connector.Request 的 parseParameters 方法,这个方法将会对传递的参数进行解码。下面代码只是parseParameters方法的一部分:

          //获取编码 String enc = getCharacterEncoding(); //获取ContentType 中定义的 Charset boolean useBodyEncodingForURI = connector.getUseBodyEncodingForURI(); if (enc != null) { //若是设置编码不为空,则设置编码为enc parameters.setEncoding(enc); if (useBodyEncodingForURI) { //若是设置了Chartset,则设置queryString的解码为ChartSet parameters.setQueryStringEncoding(enc); } } else { //设置默认解码方式 parameters.setEncoding(org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING); if (useBodyEncodingForURI) { parameters.setQueryStringEncoding(org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING); } }

从上面代码能够看出对query String的解码格式要么采用设置的ChartSet要么采用默认的解码格式ISO-8859-1。注意这个设置的ChartSet是在 http Header中定义的ContentType,同时若是咱们须要改指定属性生效,还须要进行以下配置:

<Connector URIEncoding="UTF-8" useBodyEncodingForURI="true"/>

上面部分详细介绍了URL方式请求的编码解码过程。其实对于咱们而言,咱们更多的方式是经过表单的形式来提交。

表单GET

咱们知道经过URL方式提交数据是很容易产生乱码问题的,因此咱们更加倾向于经过表单形式。当用户点击submit提交表单时,浏览器会更加设定的 编码来编码数据传递给服务器。经过GET方式提交的数据都是拼接在URL后面(能够当作query String??)来提交的,因此tomcat服务器在进行解码过程当中URIEncoding就起到做用了。tomcat服务器会根据设置的 URIEncoding来进行解码,若是没有设置则会使用默认的ISO-8859-1来解码。假如咱们在页面将编码设置为UTF-8,而 URIEncoding设置的不是或者没有设置,那么服务器进行解码时就会产生乱码。这个时候咱们通常能够经过new String(request.getParameter(“name”).getBytes(“iso-8859-1″),”utf-8″) 的形式来获取正确数据。

表单POST

 
对于POST方式,它采用的编码也是由页面来决定的即contentType。当我经过点击页面的submit按钮来提交表单时,浏览器首先会根据 ontentType的charset编码格式来对POST表单的参数进行编码而后提交给服务器,在服务器端一样也是用contentType中设置的字 符集来进行解码(这里与get方式就不一样了),这就是经过POST表单提交的参数通常而言都不会出现乱码问题。固然这个字符集编码咱们是能够本身设定 的:request.setCharacterEncoding(charset) 。
 
/////////////////////////////////////////////////////
JSP页面编码过程:

咱们知道JSP页面是须要转换为servlet的,在转换过程当中确定是要进行编码的。在JSP转换为servlet过程当中下面一段代码起到相当重要的做用。

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

在上面代码中有两个地方存在编码:pageEncoding、contentType的charset。其中pageEncoding是jsp文件自己的编码,而contentType的charset是指服务器发送给客户端时的内容编码。

在前面一篇博客中就提到过(java中文乱码解决之道(四)—–java编码转换过程)jsp在转换为Servlet的过程当中是须要通过主要的三次编码转换过程(除去数据库编码转换、页面参数输入编码转换):

第一次:转换为.java文件;

第二次:转换为.class文件;

第三次:业务逻辑处理后输出。

第一阶段

JVM将JSP编译为.jsp文件。在这个过程当中pageEncoding就起到做用了,JVM首先会获取pageEncoding的值,若是该值存在则采用它设定的编码来编译,不然则采用file.encoding编码来编译。

第二阶段

JVM将.java文件转换为.class文件。在这个过程就与任何编码的设置都没有关系了,无论JSP采用了什么样的编码格式都将无效。通过这个阶段后.jsp文件就转换成了统一的Unicode格式的.class文件了。

第三阶段

  1. 后台通过业务逻辑处理后将产生的结果输出到客户端。在这个过程当中contentType的charset就发挥了功效。若是设置了charset则浏览器就会使用指定的编码格式进行解码,不然采用默认的ISO-8859-1编码格式进行解码处理。

流程如以下:

 
 
解决URL中文乱码问题:

咱们主要经过两种形式提交向服务器发送请求:URL、表单。而表单形式通常都不会出现乱码问题,乱码问题主要是在URL上面。经过前面几篇博客的介 绍咱们知道URL向服务器发送请求编码过程实在是实在太混乱了。不一样的操做系统、不一样的浏览器、不一样的网页字符集,将致使彻底不一样的编码结果。若是程序员 要把每一种结果都考虑进去,是否是太恐怖了?有没有办法,可以保证客户端只用一种编码方法向服务器发出请求?

 
有!这里我主要提供如下几种方法

1、javascript

使用javascript编码不给浏览器插手的机会,编码以后再向服务器发送请求,而后在服务器中解码。在掌握该方法的时候,咱们须要料及javascript编码的三个方法:escape()、encodeURI()、encodeURIComponent()。

escape

采用SIO Latin字符集对指定的字符串进行编码。全部非ASCII字符都会被编码为%xx格式的字符串,其中xx表示该字符在字符集中所对应的16进制数字。例如,格式对应的编码为%20。它对应的解码方法为unescape()。

 
事实上escape()不能直接用于URL编码,它的真正做用是返回一个字符的Unicode编码值。好比上面“我是cm”的结果为%u6211%u662Fcm,其中“我”对应的编码为6211,“是”的编码为662F,“cm”编码为cm。

注意,escape()不对”+”编码。可是咱们知道,网页在提交表单的时候,若是有空格,则会被转化为+字符。服务器处理数据的时候,会把+号处理成空格。因此,使用的时候要当心。

encodeURI

对整个URL进行编码,它采用的是UTF-8格式输出编码后的字符串。不过encodeURI除了ASCII编码外对于一些特殊的字符也不会进行编码如:! @ # $& * ( ) = : / ; ? + ‘。

encodeURIComponent()

把URI字符串采用UTF-8编码格式转化成escape格式的字符串。相对于encodeURI,encodeURIComponent会更增强 大,它会对那些在encodeURI()中不被编码的符号(; / ? : @ & = + $ , #)通通会被编码。可是encodeURIComponent只会对URL的组成部分进行个别编码,而不用于对整个URL进行编码。对应解码函数方法 decodeURIComponent。

固然咱们通常都是使用encodeURI方来进行编码操做。所谓的javascript两次编码后台两次解码就是使用该方法。javascript解决该问题有一次转码、两次转码两种解决方法。

一次转码

javascript转码:

var url = '<s:property value="webPath" />/ShowMoblieQRCode.servlet?name=我是cm'; window.location.href = encodeURI(url); 

转码后的URL:http://127.0.0.1:8080/perbank/ShowMoblieQRCode.servlet?name=%E6%88%91%E6%98%AFcm

后台处理:

String name = request.getParameter("name");
        System.out.println("前台传入参数:" + name); name = new String(name.getBytes("ISO-8859-1"),"UTF-8"); System.out.println("通过解码后参数:" + name); 

输出结果:

前台传入参数:??????cm
通过解码后参数:我是cm

二次转码

javascript

var url = '<s:property value="webPath" />/ShowMoblieQRCode.servlet?name=我是cm'; window.location.href = encodeURI(encodeURI(url)); 

转码后的url:http://127.0.0.1:8080/perbank/ShowMoblieQRCode.servlet?name=%25E6%2588%2591%25E6%2598%25AFcm

后台处理:

        String name = request.getParameter("name");
        System.out.println("前台传入参数:" + name); name = URLDecoder.decode(name,"UTF-8"); System.out.println("通过解码后参数:" + name); 

输出结果:

前台传入参数:E68891E698AFcm
通过解码后参数:我是cm

filter

使用过滤器,过滤器LZ提供两种,第一种设置编码,第二种直接在过滤器中进行解码操做。

过滤器1

该过滤器是直接设置request的编码格式的。

public class CharacterEncoding implements Filter { private FilterConfig config ; String encoding = null; public void destroy() { config = null; } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { request.setCharacterEncoding(encoding); chain.doFilter(request, response); } public void init(FilterConfig config) throws ServletException { this.config = config; //获取配置参数 String str = config.getInitParameter("encoding"); if(str!=null){ encoding = str; } } }

配置:

<!-- 中文过滤器的配置 --> <filter> <filter-name>chineseEncoding</filter-name> <filter-class>com.test.filter.CharacterEncoding</filter-class> <init-param> <param-name>encoding</param-name> <param-value>utf-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>chineseEncoding</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>

过滤器2

 
该过滤器在处理方法中将参数直接进行解码操做,而后将解码后的参数从新设置到request的attribute中。

 

public class CharacterEncoding implements Filter { protected FilterConfig filterConfig ; String encoding = null; public void destroy() { this.filterConfig = null; } /** * 初始化 */ public void init(FilterConfig filterConfig) { this.filterConfig = filterConfig; } /** * 将 inStr 转为 UTF-8 的编码形式 * * @param inStr 输入字符串 * @return UTF - 8 的编码形式的字符串 * @throws UnsupportedEncodingException */ private String toUTF(String inStr) throws UnsupportedEncodingException { String outStr = ""; if (inStr != null) { outStr = new String(inStr.getBytes("iso-8859-1"), "UTF-8"); } return outStr; } /** * 中文乱码过滤处理 */ public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse) servletResponse; // 得到请求的方式 (1.post or 2.get), 根据不一样请求方式进行不一样处理 String method = request.getMethod(); // 1. 以 post 方式提交的请求 , 直接设置编码为 UTF-8 if (method.equalsIgnoreCase("post")) { try { request.setCharacterEncoding("UTF-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } // 2. 以 get 方式提交的请求 else { // 取出客户提交的参数集 Enumeration<String> paramNames = request.getParameterNames(); // 遍历参数集取出每一个参数的名称及值 while (paramNames.hasMoreElements()) { String name = paramNames.nextElement(); // 取出参数名称 String values[] = request.getParameterValues(name); // 根据参数名称取出其值 // 若是参数值集不为空 if (values != null) { // 遍历参数值集 for (int i = 0; i < values.length; i++) { try { // 回圈依次将每一个值调用 toUTF(values[i]) 方法转换参数值的字元编码 String vlustr = toUTF(values[i]); values[i] = vlustr; } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } // 将该值以属性的形式藏在 request request.setAttribute(name, values); } } } // 设置响应方式和支持中文的字元集 response.setContentType("text/html;charset=UTF-8"); // 继续执行下一个 filter, 无一下个 filter 则执行请求 chain.doFilter(request, response); } }

配置:

<!-- 中文过滤器的配置 --> <filter> <filter-name>chineseEncoding</filter-name> <filter-class>com.test.filter.CharacterEncoding</filter-class> </filter> <filter-mapping> <filter-name>chineseEncoding</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>

其余

一、设置pageEncoding、contentType

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

二、设置tomcat的URIEncoding

 
在默认状况下,tomcat服务器使用的是ISO-8859-1编码格式来编码的,URIEncoding参数对get请求的URL进行编码,因此 咱们只须要在tomcat的server.xml文件的<Connector>标签中加上URIEncoding=”utf-8″便可。
相关文章
相关标签/搜索