深刻剖析Java编程中的中文问题及建议最优解决方法

摘录自:http://fafeng.blogbus.com/logs/3062998.html    html

    http://www.blogbus.com/fafeng-logs/3063006.htmljava

深刻剖析Java编程中的中文问题及建议最优解决方法mysql

说明:本文为做者原创,做者联系地址为:josserchai@yahoo.com。因为Java编程中的中文问题是一个老生常谈的问题,在阅读了许多关于Java中文问题解决方法以后,结合做者的编程实践,我发现过去谈的许多方法都不能清晰地说明问题及解决问题,尤为是跨平台时的中文问题。因而我给出此篇文章,内容包括对控制台运行的class、Servelets、JSP及EJB类中的中文问题我剖析和建议解决办法。但愿你们指教。任何引用本文请注明出处!! 
Abstract:本文深刻分析了Java程序设计中Java编译器对java源文件和JVM对class类文件的编码/解码过程,经过此过程的解析透视出了Java编程中中文问题产生的根本缘由,最后给出了建议的最优化的解决Java中文问题的方法。 
1、中文问题的来源 
    计算机最初的操做系统支持的编码是单字节的字符编码,因而,在计算机中一切处理程序最初都是以单字节编码的英文为准进行处理。随着计算机的发展,为了适应世界其它民族的语言(固然包括咱们的汉字),人们提出了UNICODE编码,它采用双字节编码,兼容英文字符和其它民族的双字节字符编码,因此,目前,大多数国际性的软件内部均采用UNICODE编码,在软件运行时,它得到本地支持系统(多数时间是操做系统)默认支持的编码格式,而后再将软件内部的UNICODE转化为本地系统默认支持的格式显示出来。Java的JDK和JVM便是如此,我这里说的JDK是指国际版的JDK,咱们大多数程序员使用的是国际化的JDK版本,如下全部的JDK均指国际化的JDK版本。咱们的汉字是双字节编码语言,为了能让计算机处理中文,咱们本身制定的gb23十二、GBK、GBK2K等标准以适应计算机处理的需求。因此,大部分的操做系统为了适应咱们处理中文的需求,均定制有中文操做系统,它们采用的是GBK,GB2312编码格式以正确显示咱们的汉字。如:中文Win2K默认采用的是GBK编码显示,在中文WIN2k中保存文件时默认采用的保存文件的编码格式也是GBK的,即,全部在中文WIN2K中保存的文件它的内部编码默认均采用GBK编码,注意:GBK是在GB2312基础上扩充来的。
    因为Java语言内部采用UNICODE编码,因此在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中的javac.exe来编译这些源代码,造成.class类(JSP文件是由容器调用JDK来编译的);
     *直接运行这些类或将这些类布署到WEB容器中去运行,并输出结果。
    那么,在这些过程当中,JDK和JVM是如何将这些文件如何编码和解码并运行的呢?
    这里,咱们以中文win2k操做系统为例说明JAVA类是如何来编码和被解码的。
    第一步,咱们在中文win2k中用编辑软件如记事本编写一个Java源程序文件(包括以上五类JAVA程序),程序文件在保存时默认采用了操做系统默认支持GBK编码格式(操做系统默认支持的格式为file.encoding格式)造成了一个.java文件,也即,java程序在被编译前,咱们的JAVA源程序文件是采用操做系统默认支持的file.encoding编码格式保存的,java源程序中含有中文信息字符和英文程序代码;要查看系统的file.encoding参数,能够用如下代码:
public class ShowSystemDefaultEncoding {
public static void main(String[] args) {
String encoding = System.getProperty("file.encoding");
System.out.println(encoding);
}}linux

  
    第二步,咱们用JDK的javac.exe文件编译咱们的Java源程序,因为JDK是国际版的,在编译的时候,若是咱们没有用-encoding参数指定咱们的JAVA源程序的编码格式,则javac.exe首先得到咱们操做系统默认采用的编码格式,也即在编译java程序时,若咱们不指定源程序文件的编码格式,JDK首先得到操做系统的file.encoding参数(它保存的就是操做系统默认的编码格式,如WIN2k,它的值为GBK),而后JDK就把咱们的java源程序从file.encoding编码格式转化为JAVA内部默认的UNICODE格式放入内存中。而后,javac把转换后的unicode格式的文件进行编译成.class类文件,此时.class文件是UNICODE编码的,它暂放在内存中,紧接着,JDK将此以UNICODE编码的编译后的class文件保存到咱们的操做系统中造成咱们见到的.class文件。对咱们来讲,咱们最终得到的.class文件是内容以UNICODE编码格式保存的类文件,它内部包含咱们源程序中的中文字符串,只不过此时它己经由file.encoding格式转化为UNICODE格式了。
    这一步中,对于JSP源程序文件是不一样的,对于JSP,这个过程是这样的:即WEB容器调用JSP编译器,JSP编译器先查看JSP文件中是否设置有文件编码格式,若是JSP文件中没有设置JSP文件的编码格式,则JSP编译器调用JDK先把JSP文件用JVM默认的字符编码格式(也即WEB容器所在的操做系统的默认的file.encoding)转化为临时的Servlet类,而后再把它编译成UNICODE格式的class类,并保存在临时文件夹中。如:在中文win2k上,WEB容器就把JSP文件从GBK编码格式转化为UNICODE格式,而后编译成临时保存的Servlet类,以响应用户的请求。
    程序员

  第三步,运行第二步编译出来的类,分为三种状况:
    A、 直接在console上运行的类
    B、 EJB类和不能够直接运行的支持类(如JavaBean类)
    C、 JSP代码和Servlet类
    D、 JAVA程序和数据库之间
    下面咱们分这四种状况来看。
    A、直接在console上运行的类
    这种状况,运行该类首先须要JVM支持,即操做系统中必须安装有JRE。运行过程是这样的:首先java启动JVM,此时JVM读出操做系统中保存的class文件并把内容读入内存中,此时内存中为UNICODE格式的class类,而后JVM运行它,若是此时此类须要接收用户输入,则类会默认用file.encoding编码格式对用户输入的串进行编码并转化为unicode保存入内存(用户能够设置输入流的编码格式)。程序运行后,产生的字符串(UNICODE编码的)再回交给JVM,最后JRE把此字符串再转化为file.encoding格式(用户能够设置输出流的编码格式)传递给操做系统显示接口并输出到界面上。
    对于这种直接在console上运行的类,它的转化过程可用图1更加明确的表示出来sql

   

    以上每一步的转化都须要正确的编码格式转化,才能最终不出现乱码现象。
    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容器会默认采用ISO-8859-1编码格式来接受传入的值并在JVM中转化为UNICODE格式的保存在WEB容器的内存中。Servlet运行后生成输出,输出的字符串是UNICODE格式的,紧接着,容器将Servlet运行产生的UNICODE格式的串(如html语法,用户输出的串等)直接发送到客户端浏览器上并输出给用户,若是此时指定了发送时输出的编码格式,则按指定的编码格式输出到浏览器上,若是没有指定,则默认按ISO-8859-1编码发送到客户的浏览器上。
     这种JSP代码和Servlet类,它的转化过程可用图3更加明确地表示出来:
  
编程

    D、Java程序和数据库之间
    对于几乎全部数据库的JDBC驱动程序,默认的在JAVA程序和数据库之间传递数据都是以ISO-8859-1为默认编码格式的,因此,咱们的程序在向数据库内存储包含中文的数据时,JDBC首先是把程序内部的UNICODE编码格式的数据转化为ISO-8859-1的格式,而后传递到数据库中,在数据库保存数据时,它默认即以ISO-8859-1保存,因此,这是为何咱们经常在数据库中读出的中文数据是乱码。windows

对于JAVA程序和数据库之间的数据传递,咱们能够用图4清晰地表示出来数组

   

3、分析常见的JAVA中文问题几个必须清楚的原则
    首先,通过上面的详细分析,咱们能够清晰地看到,任何JAVA程序的生命期中,其编码转换的关键过程是在于:最初编译成class文件的转码和最终向用户输出的转码过程。
    其次,咱们必须了解JAVA在编译时支持的、经常使用的编码格式有如下几种:
    *ISO-8859-1,8-bit, 同8859_1,ISO-8859-1,ISO_8859_1等编码
    *Cp1252,美国英语编码,同ANSI标准编码
    *UTF-8,同unicode编码
    *GB2312,同gb2312-80,gb2312-1980等编码
    *GBK , 同MS936,它是gb2312的扩充
    及其它的编码,如韩文、日文、繁体中文等。同时,咱们要注意这些编码间的兼容关体系以下:
    unicode和UTF-8编码是一一对应的关系。GB2312能够认为是GBK的子集,即GBK编码是在gb2312上扩展来的。同时,GBK编码包含了20902个汉字,编码范围为:0x8140-0xfefe,全部的字符能够一一对应到UNICODE2.0中来。
    再次,对于放在操做系统中的.java源程序文件,在编译时,咱们能够指定它内容的编码格式,具体来讲用-encoding来指定。注意:若是源程序中含有中文字符,而你用-encoding指定为其它的编码字符,显然是要出错的。用-encoding指定源文件的编码方式为GBK或gb2312,不管咱们在什么系统上编译含有中文字符的JAVA源程序都不会有问题,它都会正确地将中文转化为UNICODE存储在class文件中。
    而后,咱们必须清楚,几乎全部的WEB容器在其内部默认的字符编码格式都是以ISO-8859-1为默认值的,同时,几乎全部的浏览器在传递参数时都是默认以UTF-8的方式来传递参数的。因此,虽然咱们的Java源文件在出入口的地方指定了正确的编码方式,但其在容器内部运行时仍是以ISO-8859-1来处理的。

四、中文问题的分类及其建议最优解决办法
    了解以上JAVA处理文件的原理以后,咱们就能够提出了一套建议最优的解决汉字问题的办法。
    咱们的目标是:咱们在中文系统中编辑的含有中文字符串或进行中文处理的JAVA源程序经编译后能够移值到任何其它的操做系统中正确运行,或拿到其它操做系统中编译后能正确运行,能正确地传递中文和英文参数,能正确地和数据库交流中英文字符串。
    咱们的具体思路是:在JAVA程序转码的入口和出口及JAVA程序同用户有输入输出转换的地方限制编码方法使之正确便可。
    具体解决办法以下:
    一、 针对直接在console上运行的类
    对于这种状况,咱们建议在程序编写时,若是须要从用户端接收用户的可能含有中文的输入或含有中文的输出,程序中应该采用字符流来处理输入和输出,具体来讲,应用如下面向字符型节点流类型:
    对文件:FileReader,FileWrieter 
        其字节型节点流类型为:FileInputStream,FileOutputStream
    对内存(数组):CharArrayReader,CharArrayWriter
        其字节型节点流类型为:ByteArrayInputStream,ByteArrayOutputStream
    对内存(字符串):StringReader,StringWriter
    对管道:PipedReader,PipedWriter
        其字节型节点流类型为:PipedInputStream,PipedOutputStream
    同时,应该用如下面向字符型处理流来处理输入和输出:
    BufferedWriter,BufferedReader
        其字节型的处理流为:BufferedInputeStream,BufferedOutputStream
    InputStreamReader,OutputStreamWriter
    其字节型的处理流为:DataInputStream,DataOutputStream
    其中InputStreamReader和InputStreamWriter用于将字节流按照指定的字符编码集转换到字符流,如:
    InputStreamReader in = new InputStreamReader(System.in,"GB2312");
    OutputStreamWriter out = new OutputStreamWriter (System.out,"GB2312");
    例如:采用以下的示例JAVA编码就达到了要求:
//Read.java
import java.io.*;
public class Read {
public static void main(String[] args) throws IOException {
String str = " 中文测试,这是内部硬编码的串"+" test english character";
String strin= "";
BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in,"gb2312")); //设置输入接口按中文编码
BufferedWriter stdout = new BufferedWriter(new OutputStreamWriter(System.out,"gb2312")); //设置输出接口按中文编码
stdout.write("请输入:");
stdout.flush();
strin = stdin.readLine();
stdout.write("这是从用户输入的串:"+strin);
stdout.write(str);
stdout.flush();
}}
    同时,在编译程序时,咱们用如下方式来进行:
    javac -encoding gb2312 Read.java
    其运行结果如图5所示:

  

    二、 针对EJB类和不能够直接运行的支持类(如JavaBean类)
    因为这种类它们自己被其它的类调用,不直接与用户交互,故对这种类来讲,咱们的建议的处理方式是内部程序中应该采用字符流来处理程序内部的中文字符串(具体如上面一节中同样),同时,在编译类时用-encoding gb2312参数指示源文件是中文格式编码的便可。
    三、 针对Servlet类
    针对Servlet,咱们建议用如下方法:
    在编译Servlet类的源程序时,用-encoding指定编码为GBK或GB2312,且在向用户输出时的编码部分用response对象的setContentType("text/html;charset=GBK");或gb2312来设置输出编码格式,一样在接收用户输入时,咱们用request.setCharacterEncoding("GB2312");这样不管咱们的servlet类移植到什么操做系统中,只有客户端的浏览器支持中文显示,就能够正确显示。以下是一个正确的示例:

   

    其运行结果如图6所示:
  

    四、 JAVA程序和数据库之间
    为避免JAVA程序和数据库之间数据传递出现乱码现象,咱们建议采用如下最优方法来处理:
    一、 对于JAVA程序的处理方法按咱们指定的方法处理。
    二、 把数据库默认支持的编码格式改成GBK或GB2312的。
    如:在mysql中,咱们能够在配置文件my.ini中加入如下语句实现:
    在[mysqld]区增长:
    default-character-set=gbk
    并增长:
    [client]
    default-character-set=gbk
    在SQL Server2K中,咱们能够将数据库默认的语言设置为Simplified Chinese来达到目的。
    五、 针对JSP代码
    因为JSP是在运行时,由WEB容器进行动态编译的,若是咱们没有指定JSP源文件的编码格式,则JSP编译器会得到服务器操做系统的file.encoding值来对JSP文件编译的,它在移植时最容易出问题,如在中文win2k中能够很好运行的jsp文件拿到英文linux中就不行,尽管客户端都是同样的,那是由于容器在编译JSP文件时获取的操做系统的编码不一样形成的(在中文wink中的file.encoding和在英文Linux中file.encoding是不一样的,且英文Linux的file.encoding对中文不支持,因此编译出来的JSP类就会有问题)。网络上讨论的大多数是此类问题,可能是由于JSP文件移植平台时不能正确显示的问题,对于这类问题,咱们了解了JAVA中程序编码转换的原理,解决起来就容易多了。咱们建议的解决办法以下:
    一、咱们要保证JSP向客户端输出时是采用中文编码方式输出的,即不管如何咱们首先在咱们的JSP源代编中加入如下一行:
    
    二、为了让JSP能正确得到传入的参数,咱们在JSP源文件头加入下面一句:
    
    三、为了让JSP编译器能正确地解码咱们的含有中文字符的JSP文件,咱们须要在JSP源文件中指定咱们的JSP源文件的编码格式,具体来讲,咱们在JSP源文件头上加入下面的一句便可:
    或
    这是JSP规范2.0新增长的指令。
    咱们建议使用此方法来解JSP文件中的中文问题,下面的代码是一个正确作法的JSP文件的测试程序:

  

    如图7是此程序运行的结果示意图:

  

5、总结
    在上面的详细分析中,咱们清晰地给出了JAVA在处理源程序过程当中的详细转换过程,为咱们正确解决JAVA编程中的中文问题提供了基础。同时,咱们给出了认为是最优的解决JAVA中文问题的办法。
 6、参考资料
一、段明辉.Java 编程技术中汉字问题的分析及解决.

http://www-900.ibm.com/developerWorks/cn/java/java_chinese/index.shtml
二、 周竞涛.关于Java中文问题的几条分析原则.
http://www-900.ibm.com/developerWorks/cn/java/l-javachinese/index.shtml
三、做者介绍.

做者:abnerchai,高级程序员,做者联系方法:josserchai@yahoo.com .

相关文章
相关标签/搜索