Java中⽂乱码字符集解决⼤全-
转⾃:
Abstract:本⽂深⼊分析了Java程序设计中Java编译器对java源⽂件和JVM对class类⽂件的编码/解码过程,通过此过程的解析透视出了Java编程中中⽂问题产⽣的根本原因,最后给出了建议的最优化的解决Java中⽂问题的⽅法。
  1、中⽂问题的来源
计算机最初的操作系统⽀持的编码是单字节的字符编码,于是,在计算机中⼀切处理程序最初都是以单字节编码的英⽂为准进⾏处理。随着计算机的发展,为了适应世界其它民族的语⾔(当然包括我们的汉字),⼈们提出了UNICODE编码,它采⽤双字节编码,兼容英⽂字符和其它民族的双字节字符编码,所以,⽬前,⼤多数国际性的软件内部均采⽤UNICODE编码,在软件运⾏时,它获得本地⽀持系统(多数时间是操作系统)默认⽀持的编码格式,然后再将软件内部的UNICODE转化为本地系统默认⽀持的格式显⽰出来。Java的JDK和JVM即是如此,我这⾥说的JDK是指国际版的JDK,我们⼤多数程序员使⽤的是国际化的JDK版本,以下所有的JDK均指国际化的JDK版本。我们的汉字是双字节编码语⾔,为了能让计算机处理中⽂,我们⾃⼰制定的gb2312、GBK、GBK2K等标准以适应计算机处理的需求。所以,⼤部分的操作系统为了适应我们处理中⽂的需求,均定制有中⽂操作系统,它们采⽤的是GBK,GB2312
编码格式以正确显⽰我们的汉字。如:中⽂Win2K默认采⽤的是GBK编码显⽰,在中⽂WIN2k中保存⽂件时默认采⽤的保存⽂件的编码格式也是GBK的,即,所有在中⽂WIN2K中保存的⽂件它的内部编码默认均采⽤GBK编码,注意:GBK是在GB2312基础上扩充来的。
由于Java语⾔内部采⽤UNICODE编码(表现为编译⽣成的.class⽂件和加载进内存中的class字节码是UNICODE编码,⽽不是Java源⽂件为UNICODE编码,Java源⽂件可以为任意的编码类型),所以在JAVA程序运⾏时,就存在着⼀个从UNICODE编码和对应的操作系统及浏览器⽀持的编码格式转换输⼊、输出的问题,这个转换过程有着⼀系列的步骤,如果其中任何⼀步出错,则显⽰出来的汉字就会出是乱码,这就是我们常见的JAVA中⽂问题。
同时,Java是⼀个跨平台的编程语⾔,也即我们编写的程序不仅能在中⽂windows上运⾏,也能在中⽂Linux等系统上运⾏,同时也要求能在英⽂等系统上运⾏(我们经常看到有⼈把在中⽂win2k上编写的JAVA程序,移植到英⽂Linux上运⾏)。这种移植操作也会带来中⽂问题。
还有,有⼈使⽤英⽂的操作系统和英⽂的IE等浏览器,来运⾏带中⽂字符的程序和浏览中⽂⽹页,它们本⾝就不⽀持中⽂,也会带来中⽂问题。
⼏乎所有的浏览器默认在传递参数时都是以UTF-8编码格式来传递,⽽不是按中⽂编码传递,所以,传递中⽂参数时也会有问题,从⽽带来乱码现象。
总之,以上⼏个⽅⾯是JAVA中的中⽂问题的主要来源,我们把以上原因造成的程序不能正确运⾏⽽产⽣的问题称作:JAVA中⽂问题。
  2、JAVA编码转换的详细过程
我们常见的JAVA程序包括以下类别:
*直接在console上运⾏的类(包括可视化界⾯的类)
*JSP代码类(注:JSP是Servlets类的变型)
*Servelets类
*EJB类
*其它不可以直接运⾏的⽀持类
这些类⽂件中,都有可能含有中⽂字符串,并且我们常⽤前三类JAVA程序和⽤户直接交互,⽤于输出和输⼊字符,如:我们在JSP和Servlet中得到客户端送来的字符,这些字符也包括中⽂字符。⽆论这些JAVA类的作⽤如何,这些JAVA程序的⽣命周期都是这样的:
*编程⼈员在⼀定的操作系统上选择⼀个合适的编辑软件来实现源程序代码并以.java扩展名保存在操作系统中,例如我们在中⽂win2k中⽤记事本编辑⼀个java源程序;
*编程⼈员⽤JDK中的来编译这些源代码,形成.class类(JSP⽂件是由容器调⽤JDK来编译的);
*直接运⾏这些类或将这些类布署到WEB容器中去运⾏,并输出结果。
那么,在这些过程中,JDK和JVM是如何将这些⽂件如何编码和解码并运⾏的呢?
这⾥,我们以中⽂win2k操作系统为例说明JAVA类是如何来编码和被解码的。
第⼀步,我们在中⽂win2k中⽤编辑软件如记事本编写⼀个Java源程序⽂件(包括以上五类JAVA程序),程序⽂件在保存时默认采⽤了操作系统默认⽀持GBK编码格式(操作系统默认⽀持的格式为ding格式)形成了⼀个.java⽂件,也即,java程序在被编译前,我们的JAVA 源程序⽂件是采⽤操作系统默认⽀持的ding编码格式保存的(通过⼀些⽂本编辑器如:notepade可以指定⽂件的编码格式,⽽不使⽤操作系统默认的编码格式),java源程序中含有中⽂信息字符和英⽂程序代码;要查看系统的ding参数,可以⽤以下代码:
  public class ShowSystemDefaultEncoding {
  public static void main(String[] args) {
  String encoding = Property("ding");
  System.out.println(encoding);
  }}
第⼆步,我们⽤JDK的⽂件编译我们的Java源程序,由于JDK是国际版的,在编译的时候,如果我们没有⽤-encoding参数指定我们的JAVA源程序的编码格式,则⾸先获得我们操作系统默认采⽤的编码格式,也即在编译java程序时,若我们不指定源程序⽂
件的编码格式,JDK⾸先获得操作系统的ding参数(它保存的就是操作系统默认的编码格式,如WIN2k,它的值为GBK),然后JDK 就把我们的java源程序从ding编码格式转化为JAVA内部默认的UNICODE格式放⼊内存中。然后,javac把转换后的unicode格式的⽂件进⾏编译成.class类⽂件,此时.class⽂件是UNICODE编码的,它暂放在内存中,紧接着,JDK将此以UNICODE编码的编译后的class ⽂件保存到我们的操作系统中形成我们见到的.class⽂件(因此使⽤国际版JDK编译产⽣的所有.class⽂件都是UNICODE编码格式的,⽽不依赖于特定的操作系统,因此跨平台指的是.class⽂件,⽽不是.java源⽂件)。对我们来说,我们最终获得的.class⽂件是内容
以UNICODE编码格式保存的类⽂件,它内部包含我们源程序中的中⽂字符串,只不过此时它⼰经由ding格式转化为UNICODE格式了。
PS:实验如下:
这⾥我新建了⼀个Hello.java⽂件,经过测试,使⽤的是OS默认的字符集GB2312,如下:
使⽤EditPlus打开该⽂件进⾏编辑,并且将当前⽂件的编码⽅式修改为UTF-8,保存:
使⽤⾃⼰编写的java程序进⾏测试,确实是UTF-8格式:
然后对该⽂件进⾏编译,正常,执⾏时发现乱码,如下:
这个是因为JDK在默认情况下使⽤了OS的字符集进⾏解码的结果,即使⽤了中⽂操作系统的GB2312字符集进⾏了解码,但实际上Hello.java⽂件使⽤的是UTF-8的⽅式编码,编码解码⽅式不⼀致就出现了乱码状态。
解决问题的⽅式如下:
java语言使用的字符码集是
即使⽤-encoding utf-8参数对Hello.java源⽂件进⾏重新编译。
这样就告诉编译器 “ 这⾥的Hello.java使⽤的是UTF-8进⾏编码的,请按照UTF-8的⽅式进⾏解码然后编译 ”,随后编译器做完这些事情之后会使⽤UNICODE编码并存储编译好的.class⽂件。
这⼀步中,对于JSP源程序⽂件是不同的,对于JSP,这个过程是这样的:即WEB容器调⽤JSP编译器,JSP编译器先查看JSP⽂件中是否设置有⽂件编码格式,如果JSP⽂件中没有设置JSP⽂件的编码格式,则JSP编译器调⽤JDK先把JSP⽂件⽤JVM默认的字符编码格式(也即WEB容器所在的操作系统的默认的ding)转化为临时的Servlet类(此时转化为.java临时⽂件存储于外存中,并且经过查看发现是UNICODE格式--tomcat),然后再把它编译成UNICODE格式的class类,并保存在临时⽂件夹中。如:在中⽂win2k上,WEB容器就把JSP ⽂件从GBK编码格式转化为UNICODE格式,然后编译成临时保存的Servlet类,以响应⽤户的请求。
注意:这⾥需要解释⼀下,JSP含有指令标签<%@ page language="java" pageEncoding="utf-8"%>,pageEncoding属性指的是当前jsp页⾯⽂件在编写的时候指定的底层实际编码⽅式两者需要⼀致,如果JSP⽂件的具体编码⽅式与page指令元素指定的编码⽅式不有任何不⼀致的情况,则会发⽣乱码情况,这是因为web容器可以⾸先分析JSP页⾯,因为指定页⾯编码⽅式的page指令元素是标准的英⽂格式(任何字符集中都包含有标准英⽂字符集),因此可以通过分析jsp⽂件获取到当前⽂件的
编码格式,如例中的UTF-8,然后,再根据这个编码⽅式对当前JSP⽂件进⾏解码,随后被JSP编译器按照JAVA的UNICODE转换为Servlet类临时⽂件,最后被JRE加载⼊内存中相应⽤户的请求。
第三步,运⾏第⼆步编译出来的类,分为三种情况:
A、直接在console上运⾏的类
B、 EJB类和不可以直接运⾏的⽀持类(如JavaBean类)
C、 JSP代码和Servlet类
D、 JAVA程序和数据库之间
下⾯我们分这四种情况来看。
A、直接在console上运⾏的类
这种情况,运⾏该类⾸先需要JVM⽀持,即操作系统中必须安装有JRE。运⾏过程是这样的:⾸先java启动JVM,此时JVM读出操作系统中保存的class⽂件并把内容读⼊内存中,此时内存中为UNICODE格式的class类,然后JVM运⾏它,如果此时此类需要接收⽤户输⼊,则类会默认⽤
ding编码格式对⽤户输⼊的串进⾏编码并转化为UNICODE保存⼊内存(⽤户可以设置输⼊流的编码格式)。程序运⾏后,产⽣的字符串(UNICODE编码的)再回交给JVM,最后JRE把此字符串再转化为ding格式(⽤户可以设置输出流的编码格式)传递给操作系统显⽰接⼝并输出到界⾯上。
对于这种直接在console上运⾏的类,它的转化过程可⽤图1更加明确的表⽰出来:
以上每⼀步的转化都需要正确的编码格式转化,才能最终不出现乱码现象。
B、EJB类和不可以直接运⾏的⽀持类(如JavaBean类)
由于EJB类和不可以直接运⾏的⽀持类,它们⼀般不与⽤户直接交互输⼊和输出,它们常常与其它的类进⾏交互输⼊和输出,所以它们在第⼆步被编译后,就形成了内容是UNICODE编码的类保存在操作系统中了,以后只要它与其它的类之间的交互在参数传递过程中没有丢失,则它就会正确的运⾏。
这种EJB类和不可以直接运⾏的⽀持类, 它的转化过程可⽤图2更加明确的表⽰出来:
C、JSP代码和Servlet类
经过第⼆步后,JSP⽂件也被转化为Servlets类⽂件,只不过它不像标准的Servlets⼀校存在于classes⽬录中,它存在于WEB容器的临时⽬录中,故这⼀步中我们也把它做为Servlets来看。
对于Servlets,客户端请求它时,WEB容器调⽤它的JVM来运⾏Servlet,⾸先,JVM把Servlet的class类从系统中读出并装⼊内存中,内存中是以UNICODE编码的Servlet类的代码,然后JVM在内存中运⾏该Servlet类,如果Servlet在运⾏的过程中,需要接受从客户端传来的字符如:表单输⼊的值和URL中传⼊的值,此时如果程序中没有设定接受参数时采⽤的编码格式,则WEB容器(注意将WEB容器内部编码⽅式与JDK使⽤的编码⽅式进⾏区别,这是两个系统,WEB容器使⽤JDK进⾏编译运⾏)会默认采⽤ISO-8859-1编码格式来接受传⼊的值并在JVM中转化为UNICODE格式的保存在WEB容器的内存中。Servlet运⾏后⽣成输出,输出的字符串是UNICODE格式的,紧接着,容器将Servlet运⾏产⽣的UNICODE格式的串(如html语法,⽤户输出的串等)直接发送到客户端浏览器上并输出给⽤户,如果此时指定了发送时输出的编码格式,则按指定的编码格式输出到浏览器上,如果没有指定,则默认按ISO-8859-1编码发送到客户的浏览器上。这种JSP代码和Servlet类,它的转化过程可⽤图3更加明确地表⽰出来:
------------------------------------------------
JSP&Servlet实验:
这样来做⼏个实验:
从⽤户输⼊URL请求页⾯,到⽤户提交表单返回正确页⾯整个过程分析字符集的转换过程:
1>⽤户请求JSP页⾯:
  A. JSP页⾯的page指令元素的 pageEncoding属性指定的是当前页⾯的编码格式,⼀定要与当前页⾯⽂件的实际编码格式⼀致,容器编译会发⽣乱码;
  B. page指令元素的 contentType属性指定的是当前页⾯内容在内部的UNICODE格式经过处理之后转换为何种编码返回给⽤户浏览器,例如<%@ page contentType="text/html;charset=GBK"%>
指的是,在虚拟机内部将UNICODE编码格式的字符串转换为GBK的编码格式的字节流返回给⽤户的浏览器,⽽不是默认的ISO-8859-1返回给浏览器。
  C. 在JSP中的脚本段通过 response.setCharacterEncoding("utf-8");指定的返回字符编码格式将会覆盖掉指令元素 <%page contentType="text/html;charset=GBK"%>指定的字符编码格式.(经过测试)
如下:
<%@ page pageEncoding="utf-8" contentType="text/html; charset=ISO-8859-1"%>
<html>
<head>
<title>Test Character</title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
</head>
<body>
<form action="chartest" method="POST" target="_self">
id:<input name="id" type="text" value="" size="30">
name:<input name="name" type="text" value="" size="30">
<input type="submit" value="提交">
</form>
</body>

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。