字符编码

字符:人们使用的记号,抽象意义上的一个符号。比如:‘1’,‘中’,‘a’字节:计算机中存储数据的单元,一个8位的二进制数,是一个很具体的存储空间字符集:使用哪些字符。也就是说哪些汉字,字母和符号会被收入标准中。所包含“字符”的集合就叫做“字符集”。编码:规定每个“字符”分别用一个字节还是多个字节存储,用哪些字节来存储,这个规定就叫做“编码”平常我们所说的“字符集”,比如:GB2312,GBK,JIS等,除了有“字符的集合”这层含义外,同时也包含了“编码”的含义。

一、编码基本知识

1.iso8859-1属于单字节编码,最多能表示的字符范围是0-255,应用于英文系列。比如,字母'a'的编码为0x61=97。很明显,iso8859-1编码表示的字符范围很窄,无法表示中文字符。但是,由于是单字节编码,和计算机最基础的表示单位一致,所以很多时候,仍旧使用iso8859-1编码来表示。而且在很多协议上,默认使用该编码。比如,虽然"中文"两个字不存在iso8859-1编码,以gb2312编码为例,应该是"d6d0cec4"两个字符,使用iso8859-1编码的时候则将它拆开为4个字节来表示:"d6d0cec4"(事实上,在进行存储的时候,也是以字节为单位处理的)。而如果是UTF编码,则是6个字节"e4b8ade69687"。很明显,这种表示方法还需要以另一种编码为基础。

2.GB2312/GBK这是汉字的国标码,专门用来表示汉字,是双字节编码,而英文字母和iso8859-1一致(兼容iso8859-1编码)。其中gbk编码能够用来同时表示繁体字和简体字,而gb2312只能表示简体字,gbk是兼容gb2312编码的。

3.Unicode这是最统一的编码,可以用来表示所有语言的字符,而且是定长双字节(也有四字节的)编码,包括英文字母在内。所以可以说它是不兼容iso8859-1编码的,也不兼容任何编码。不过,相对于iso8859-1编码来说,unicode编码只是在前面增加了一个0字节,比如字母'a'为"0061"。需要说明的是,定长编码便于计算机处理(注意GB2312/GBK不是定长编码),而unicode又可以用来表示所有字符,所以在很多软件内部是使用unicode编码来处理的,比如java。

4.UTF考虑到unicode编码不兼容iso8859-1编码,而且容易占用更多的空间:因为对于英文字母,unicode也需要两个字节来表示。所以unicode不便于传输和存储。因此而产生了utf编码,utf编码兼容iso8859-1编码,同时也可以用来表示所有语言的字符,不过,utf编码是不定长编码,每一个字符的长度从1-6个字节不等。另外,utf编码自带简单的校验功能。一般来讲,英文字母都是用一个字节表示,而汉字使用三个字节。注意,虽然说utf是为了使用更少的空间而使用的,但那只是相对于unicode编码来说,如果已经知道是汉字,则使用GB2312/GBK无疑是最节省的。不过另一方面,值得说明的是,虽然utf编码对汉字使用3个字节,但即使对于汉字网页,utf编码也会比unicode编码节省,因为网页中包含了很多的英文字符。

二、JAVA对字符的处理

1.getBytes(charset)这是java字符串处理的一个标准函数,其作用是将字符串所表示的字符按照charset编码,并以字节方式表示。注意字符串在java内存中总是按unicode编码存储的。当Java程序从输入流、文件或字符文字量等途径获得字符串时,均会做字符编码的转换,例如InputStreamReader的构造函数中就需要指定编码方式,而对于从文件和字符文字量中获得字符串时,均采用系统默认的编码方式对字符数据进行解码。考虑下面一段代码:Stringstr=”中”;

①byte[]bytes=str.getBytes();

②bytes=str.getBytes(“ISO-8859-1”);

③语句①:将一个只含有一个字符“中”的字符串文字量赋给String类的一个对象str,字符文字量“中”是按照操作系统默认编码方式进行编码,在中文windows系统中通常是“GBK”,“中”在GBK编码中是0xD6D0,在将该字符赋给str时,Java会对该字符串进行编码转换,即将GBK编码方式的“中”转换成Unicode编码方式的“中”,Unicode编码方式“中”的编码是0x4E2D,所以str在程序运行期间在内存中的二进制表示成16进制就是0x4E2D。语句②:获得str字符串的二进制形式。getBytes(Stringencoding)方法需要指定编码方式,表示获得该字符串在何种编码方式中的二进制形式。此语句中没有设置参数,表示采用操作系统默认的编码方式,即此处获得的bytes是“中”在GBK编码中的二进制形式,即bytes[0]=0xD6,bytes[1]=0xD0。语句③:该语句与语句②的区别就是指定了编码方式,此处指定的是ISO-8859-1,即通常所说的Latin-1,该编码采用8bit对字符编码,所以编码空间中只有256个字符。该编码中只包含了基本的ASCII码和一些扩展的其它西欧字符,所以该字符集中不可能包含中文的“中”字,也就是说Java虚拟机无法在ISO-8859-1编码集中找到“中”字对应的编码,针对这种情况,就只返回一个问号(?,0x3f)字符,所以此时bytes.length只有1,且bytes[0]=0x3f。

2.newString(byte[]bytes,Stringencoding)getBytes()方法从字符串获得二进制的字节数组。如果要从二进制的字节数组获得字符串,则就需要使用newString(byte[]bytes,Stringencoding)方法,该方法按照encoding编码方法对字节数组bytes中的二进制数组进行解析,生成一个新的字符串对象。

byte[]bytes={(byte)0xD6,(byte)0xD0,(byte)0x31};

①Stringstr=newString(bytes);

②str=newString(bytes,”ISO-8859-1”);

③语句①:定义一个字节数组。语句②:将该字节数组中的二进制数据按照默认的编码方式(GBK)编码成字符串,我们知道GBK中0xD60xD0表示“中”,0x31表示字符“1”(GBK兼容ASCII,但不兼容ISO-8859-1除ASCII之外的部分),所以str得到的值是“中1”。语句③:该句用ISO-8859-1编码方式对该字节数据进行编码,由于在ISO-8859-1编码方式中一个字节会被解析成一个字符,所以该字节数组会被解释成包含三个字符的字符串,但由于在ISO-8859-1编码方式中没有对应0xD6和0xD0的字符,所以前两个字符会产生两个问号,由于0x31在ISO-8859-1编码中对应字符“1”(ISO-8859-1也兼容ASCII),所以此语句得到str的值是“??1”。

3.setCharacterEncoding()该函数用来设置http请求或者相应的编码。对于request,是指提交内容的编码,指定后可以通过getParameter()直接获得正确的字符串,如果不指定,则默认使用iso8859-1编码,需要进一步处理。值得注意的是在执行setCharacterEncoding()之前,不能执行任何getParameter()。而且,该指定只对POST方法有效,对GET方法无效。分析原因,应该是在执行第一个getParameter()的时候,java将会按照编码分析所有的提交内容,而后续的getParameter()不再进行分析,所以setCharacterEncoding()无效。而对于GET方法提交表单时,提交的内容在URL中,一开始就已经按照编码分析所有的提交内容,setCharacterEncoding()自然就无效。对于response,则是指定输出内容的编码,同时,该设置会传递给浏览器,告诉浏览器输出内容所采用的编码。

三、页面编码页面编码主要有两方面,一是页面本身的编码格式,即以什么编码方式保存,二是客户端浏览器以什么编码格式显示页面。

1.页面保存编码格式

1).HTML页面的编码要看你保存文件时的编码选项,多数的网页编辑软件可以让你选择编码的类型,默认为本地编码,为了使网页减少编码的问题,最好保存为UTF-8编码格式。

2).JSP页面使用下列标签指定JSP源文件的编码格式,具体来说,我们在JSP源文件头上加入下面的一句即可:%@page[/email]pageEncoding="xxx"%>,xxx可以为GB2312,GBK,UTF-8(和MySQL不同,MySQL是UTF8)等等,其默认值为ISO-8859-1。保存文件时的编码应该与xxx一致。

2.页面显示编码(通知客户端浏览器用什么字符集编码显示页面)

1).在HTML中设置页面显示编码方式使用标签设置页面显示编码

2).在Servlet中设置页面显示编码方式使用response.setContentType("text/html;charset=xxx");来指定生成的页面编码。

3).在JSP中设置页面显示编码方式使用设置页面显示编码。字符集的默认值为ISO-8859-1。

3.页面输入编码在设置页面显示编码的同时,指定了页面的输入方式。如果没有指定页面编码,则使用操作系统本身的默认编码。

四、表单传递参数编码使用表单输入数据时,处理过程如下:Userinput*(gbk:d6d0cec4)browser*(gbk:d6d0cec4)webserveriso8859-1(00d600d000ce00c4)class,需要在class中进行处理:getbytes("iso8859-1")为d6d0cec4,newString("gbk")为d6d0cec4,内存中以unicode编码则为4e2d6587。1.用户输入的编码方式和页面指定的编码有关。

2.从browser到webserver,可以在表单中指定提交内容时使用的字符集,否则会使用页面指定的编码。而如果在url中直接用?的方式输入参数,则其编码往往是操作系统本身的编码,因为这时和页面无关。

3.Webserver接收到的是字节流,默认时(getParameter)会以iso8859-1编码处理之,结果是不正确的,所以需要进行处理。但如果预先设置了编码(通过request.setCharacterEncoding()),则能够直接获取到正确的结果。

五、数据库编码

1.MySQL的字符集Mysql目前支持多字符集,并且,支持在不同的字符集之间转换(便于移植和支持多语言)。Mysql可以设置服务器级字符集、数据库级字符集、数据表级字符集、表列的字符集,实际上,最终使用字符集的地方是存储字符的列,比如,你设置table1中col1列是字符类型,col1才用到了字符集,如果table1表的col2列是int类型,col2不使用字符集的概念。服务器级字符集、数据库级字符集、数据表级字符集都是为列的字符集做默认选项的。Mysql一定有一个字符集,可以通过启动时加参数指定,也可以编译时指定,也可以在配置文件里指定。Mysql服务器字符集,只是做为数据库级的默认值。创建数据库时,你可以指定字符集,如果没指定,就使用服务器的字符集。同理,创建表时,你可以指定表级的字符集,如果没指定,使用数据库的字符集做为表的字符集。创建列时,你可以指定某列的字符集,如果没指定,就使用表的字符集。通常情况下,您只需设置服务器级的字符集,其它的数据库级,表级,以及列级的字符集,都继承自服务器级字符集。由于UTF8是最广的字符集,所以,一般情况下,我们设置Mysql服务器级的字符集为UTF8!

2.MySQL的存储机制MySQL要求客户端(mysql命令行,JDBC,PHP,CGI等)与MySQL建立连接后,必须指定客户端发送的数据采用的是什么字符集,也就是character_set_client;MySQL的怪异之处在于,得到的这个字符集并不立即转换为存储在数据库中的那个字符集,而是先转换为character_set_connection变量指定的一个字符集;转换为character_set_connection的这个字符集之后,再转换为数据库默认的字符集character_set_database进行存储;当这个数据被输出时,又要转换为character_set_results指定的字符集。上面3个变量的作用是这样的:character_set_client:设置客户端发送查询使用的字符集character_set_connection:设置服务器需要将收到的查询串转换成的字符集character_set_results:设置服务器要将结果数据转换到的字符集,转换后才发送给客户端3.JAVA与数据库在JAVA程序中使用JDBC连接数据库时,在URL中使用useUnicode=true和characterEncoding=utf-8两个属性设置Client使用的编码。如果使用MySQL4.1以上版本、MySQLJDBCDriver3.0.16以上版本,jdbc的url不用再带上useUnicode=true&EncodingCharacter=GBK,jdbc驱动程序会在连接的时候自动检测mysql服务器的变量(character_set_server)指定的编码,然后将该值赋给character_set_client,character_set_connection。使用下列语句可以查看JDBC客户端发送给服务器的SQL使用的编码:publicvoidselect()throwsSQLException{Stringurl="jdbc:mysql://localhost/database";Connectionconn=DriverManager.getConnection(url);ResultSetrs=conn.createStatement().executeQuery("SHOWVARIABLESLIKE'character_set_%'");while(rs.next()){System.out.println(rs.getString(1)+","+rs.getString(2));}rs.close();}

六、JAVA编码转换详细过程我们常见的JAVA程序包括以下类别:*直接在console上运行的类(包括可视化界面的类)*JSP代码类(注:JSP是Servlets类的变型)*Servlets类*EJB类*其它不可以直接运行的支持类这些类文件中,都有可能含有中文字符串,并且我们常用前三类JAVA程序和用户直接交互,用于输出和输入字符,如:我们在JSP和Servlet中得到客户端送来的字符,这些字符也包括中文字符。无论这些JAVA类的作用如何,这些JAVA程序的生命周期都是这样的:*编程人员在一定的操作系统上选择一个合适的编辑软件来实现源程序代码并以.java扩展名保存在操作系统中,例如我们在中文win2k中用记事本编辑一个java源程序;*编程人员用JDK中的javac.exe来编译这些源代码,形成.class类(JSP文件是由容器调用JDK来编译的);*直接运行这些类或将这些类布署到WEB容器中去运行,并输出结果。那么,在这些过程中,JDK和JVM是如何将这些文件如何编码和解码并运行的呢?这里,我们以中文windowsxp操作系统为例说明JAVA类是如何来编码和被解码的。

第一步,我们在中文win2k中用编辑软件如记事本编写一个Java源程序文件(包括以上五类JAVA程序),程序文件在保存时默认采用了操作系统默认支持的GBK编码格式(操作系统默认支持的格式为file.encoding格式)形成了一个.java文件,也即,java程序在被编译前,JAVA源程序文件是采用操作系统默认支持的file.encoding编码格式保存的;要查看系统的file.encoding参数,可以用以下代码: 

 publicclassShowSystemDefaultEncoding{  

publicstaticvoidmain(String[]args){  

Stringencoding=System.getProperty("file.encoding");  

System.out.println(encoding);  

}}

第二步,我们用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类A、直接在console上运行的类这种情况,运行该类首先需要JVM支持,即操作系统中必须安装有JRE。运行过程是这样的:首先java启动JVM,此时JVM读出操作系统中保存的class文件并把内容读入内存中,此时内存中为UNICODE格式的class类,然后JVM运行它,如果此时此类需要接收用户输入,则类会默认用file.encoding编码格式对用户输入的串进行编码并转化为unicode保存入内存(用户可以设置输入流的编码格式)。程序运行后,产生的字符串(UNICODE编码的)再回交给JVM,最后JRE把此字符串再转化为file.encoding格式(用户可以设置输出流的编码格式)传递给操作系统显示接口并输出到界面上。以上每一步的转化都需要正确的编码格式转化,才能最终不出现乱码现象。B、EJB类和不可以直接运行的支持类(如JavaBean类)由于EJB类和不可以直接运行的支持类,它们一般不与用户直接交互输入和输出,它们常常与其它的类进行交互输入和输出,所以它们在第二步被编译后,就形成了内容是UNICODE编码的类保存在操作系统中了,以后只要它与其它的类之间的交互在参数传递过程中没有丢失,则它就会正确的运行。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编译过程(以tomcat为例)

1.Tomcat先将整个JSP页面的代码读取出来,写到一个新的JAVA文件中。在读取JSP文件时,tomcat会先去读取JSP文件的pageEncoding属性,然后按照pageEncoding指定的编码来读取JSP文件。如果pageEncoding没有指定,tomcat会使用contentType指定的字符集编码,如果contentType也没有指定,就使用默认的ISO-8859-1编码。

2.Tomcat读取完JSP文件后,会用UTF-8编码将这些内容写道一个新的文件中,然后编译。

3.当JSP文件显示的时候,会使用contentType中指定的MIME类型和charset。如果charset没有指定,就使用pageEncoding中指定的编码,如果pageEncoding也没有指定,就使用默认的ISO-8859-1编码

相关推荐