java字符集编码_Java字符集编码
1.概述
在下⾯的描述中,将以"中⽂"两个字为例,经查表可以知道其GB2312编码是"d6d0 cec4",Unicode编码为"4e2d 6587",UTF编码就是"e4b8ad e69687"。注意,这两个字没有iso8859-1编码,但可以⽤iso8859-1编码来"表⽰"。
2.编码基础知识
最早的编码是iso8859-1,和ascii编码相似。但为了⽅便表⽰各种各样的语⾔,逐渐出现了很多标准编码,重要的有如下⼏个。
2.1
属于单字节编码,最多能表⽰的字符范围是0-255,应⽤于英⽂系列。⽐如,字母'a'的编码为0x61=97。
很明显,iso8859-1编码表⽰的字符范围很窄,⽆法表⽰中⽂字符。但是,由于是单字节编码,和计算机最基础的表⽰单位⼀致,所以很多时候,仍旧使⽤iso8859-1编码来表⽰。⽽且在很多协议上,默认使⽤该编码。⽐如,虽然"中⽂"两个字不存在iso8859-1编码,以
gb2312编码为例,应该是"d6d0 cec4"两个字符,使⽤iso8859-1编码的时候则将它拆开为4个字节来表⽰:"d6 d0 ce c4"(事实上,在进⾏存储的时候,也是以字节为单位处理的)。⽽如果是UTF编码,则是6个字节"e4 b8 ad e6 96 87"。很明显,这种表⽰⽅法还需要以另⼀种编码为基础。
2.2 GB2312/GBK
这就是汉字的国标码,专门⽤来表⽰汉字,是双字节编码,⽽英⽂字母和iso8859-1⼀致(兼容iso8859-1编码)。其中gbk编码能够⽤来同时表⽰繁体字和简体字,⽽gb2312只能表⽰简体字,gbk是兼容gb2312编码的。
2.3 unicode
这是最统⼀的编码,可以⽤来表⽰所有语⾔的字符,⽽且是定长双字节(也有四字节的)编码,包括英⽂字母在内。所以可以说它是不兼容iso8859-1编码的,也不兼容任何编码。不过,相对于iso8859-1编码来说,uniocode编码只是在前⾯增加了⼀个0字节,⽐如字
母'a'为"00 61"。
2.4 UTF
考虑到unicode编码不兼容iso8859-1编码,⽽且容易占⽤更多的空间:因为对于英⽂字母,unicode也需要两个字节来表⽰。所以unicode不便于传输和存储。因此⽽产⽣了utf编码,utf编码兼容iso8859-1编码,同时也可以⽤来表⽰所有语⾔的字符,不过,utf编码是不定长编码,每⼀个字符的长度从1-6个字节不等。另外,utf编码⾃带简单的校验功能。⼀般来讲,英⽂字母都是⽤⼀个字节表⽰,⽽汉字使⽤三个字节。
注意,虽然说utf是为了使⽤更少的空间⽽使⽤的,但那只是相对于unicode编码来说,如果已经知道是汉字,则使⽤GB2312/GBK⽆疑是最节省的。不过另⼀⽅⾯,值得说明的是,虽然utf编码对汉字使⽤3个字节,但即使对于汉字⽹页,utf编码也会⽐unicode编码节省,因为⽹页中包含了很多的英⽂字符。
3. java对字符的处理
在java应⽤软件中,会有多处涉及到字符集编码,有些地⽅需要进⾏正确的设置,有些地⽅需要进⾏⼀定程度的处理。
3.1. getBytes(charset)
这是java字符串处理的⼀个标准函数,其作⽤是将字符串所表⽰的字符按照charset编码,并以字节⽅
式表⽰。注意字符串在java内存中总是按unicode编码存储的。⽐如"中⽂",正常情况下(即没有错误的时候)存储为"4e2d 6587",如果charset为"gbk",则被编码为"d6d0 cec4",然后返回字节"d6 d0 ce c4"。如果charset为"utf8"则最后是"e4 b8 ad e6 96 87"。如果是"iso8859-1",则由于⽆法编码,最后返回 "3f 3f"(两个问号)。
3.2. new String(charset)
这是java字符串处理的另⼀个标准函数,和上⼀个函数的作⽤相反,将字节数组按照charset编码进⾏组合识别,最后转换为unicode存储。参考上述getBytes的例⼦,"gbk" 和"utf8"都可以得出正确的结果"4e2d 6587",但iso8859-1最后变成了"003f 003f"(两个问号)。
因为utf8可以⽤来表⽰/编码所有字符,所以new String( Bytes( "utf8" ), "utf8" ) === str,即完全可逆。
3.3. setCharacterEncoding()
该函数⽤来设置http请求或者相应的编码。
对于request,是指提交内容的编码,指定后可以通过getParameter()则直接获得正确的字符串,如果不指定,则默认使⽤iso8859-1编码,需要进⼀步处理。参见下述"表单输⼊"。值得注意的是在执⾏set
CharacterEncoding()之前,不能执⾏任何getParameter()。java doc上说明:This method must be called prior to reading request parameters or reading input using getReader()。⽽且,该指定只对POST⽅法有效,对GET⽅法⽆效。分析原因,应该是在执⾏第⼀个getParameter()的时候,java将会按照编码分析所有的提交内容,⽽后续的getParameter()不再进⾏分析,所以setCharacterEncoding()⽆效。⽽对于GET⽅法提交表单是,提交的内容在URL中,⼀开始就已经按照编码分析所有的提交内容,setCharacterEncoding()⾃然就⽆效。
对于response,则是指定输出内容的编码,同时,该设置会传递给浏览器,告诉浏览器输出内容所采⽤的编码。
3.4. 处理过程
下⾯分析两个有代表性的例⼦,说明java对编码有关问题的处理⽅法。
3.4.1. 表单输⼊
User input *(gbk:d6d0 cec4) browser *(gbk:d6d0 cec4) web server iso8859-1(00d6 00d 000ce 00c4) class,需要在class中进⾏处理:getbytes("iso8859-1")为d6 d0 ce c4,new String("gbk")为d6d0 cec4,内存中以unicode编码则为4e2d 6587。
l ⽤户输⼊的编码⽅式和页⾯指定的编码有关,也和⽤户的操作系统有关,所以是不确定的,上例以gbk为例。
l 从browser到web server,可以在表单中指定提交内容时使⽤的字符集,否则会使⽤页⾯指定的编码。⽽如果在url中直接⽤?的⽅式输⼊参数,则其编码往往是操作系统本⾝的编码,因为这时和页⾯⽆关。上述仍旧以gbk编码为例。
l Web server接收到的是字节流,默认时(getParameter)会以iso8859-1编码处理之,结果是不正确的,所以需要进⾏处理。但如果预先设置了编码(通过request. setCharacterEncoding ()),则能够直接获取到正确的结果。
l 在页⾯中指定编码是个好习惯,否则可能失去控制,⽆法指定正确的编码。
3.4.2. ⽂件编译
假设⽂件是gbk编码保存的,⽽编译有两种编码选择:gbk或者iso8859-1,前者是中⽂windows的默认编码,后者是linux的默认编码,当然也可以在编译时指定编码。
Jsp *(gbk:d6d0 cec4) java file *(gbk:d6d0 cec4) compiler read uincode(gbk: 4e2d 6587; iso8859-1: 00d6 00d 000ce
00c4) compiler write utf(gbk: e4b8ad e69687; iso8859-1: *) compiled file unicode(gbk: 4e2d 6587; iso8859-1: 00d6
00d 000ce 00c4) class。所以⽤gbk编码保存,⽽⽤iso8859-1编译的结果是不正确的。
class unicode(4e2d 6587) system.out / jsp.out gbk(d6d0 cec4) os console / browser。
l ⽂件可以以多种编码⽅式保存,中⽂windows下,默认为ansi/gbk。
l 编译器读取⽂件时,需要得到⽂件的编码,如果未指定,则使⽤系统默认编码。⼀般class⽂件,是以系统默认编码保存的,所以编译不会出问题,但对于jsp⽂件,如果在中⽂windows下编辑保存,⽽部署在英⽂linux下运⾏/编译,则会出现问题。所以需要在jsp⽂件中⽤pageEncoding指定编码。
l Java编译的时候会转换成统⼀的unicode编码处理,最后保存的时候再转换为utf编码。
l 当系统输出字符的时候,会按指定编码输出,对于中⽂windows下,System.out将使⽤gbk编码,⽽对于response(浏览器),则使⽤jsp ⽂件头指定的contentType,或者可以直接为response指定编码。同时,会告诉browser⽹页的编码。如果未指定,则会使⽤iso8859-1编码。对于中⽂,应该为browser指定输出字符串的编码。
l browser显⽰⽹页的时候,⾸先使⽤response中指定的编码(jsp⽂件头指定的contentType最终也反映在response上),如果未指定,则会使⽤⽹页中meta项指定中的contentType。
3.5. ⼏处设置
对于web应⽤程序,和编码有关的设置或者函数如下。
3.5.1. jsp编译
指定⽂件的存储编码,很明显,该设置应该置于⽂件的开头。例如:。另外,对于⼀般class⽂件,可以在编译的时候指定编码。
3.5.2. jsp输出
指定⽂件输出到browser是使⽤的编码,该设置也应该置于⽂件的开头。例如:。该设置和response.setCharacterEncoding("GBK")等效。
3.5.3. meta设置
指定⽹页使⽤的编码,该设置对静态⽹页尤其有作⽤。因为静态⽹页⽆法采⽤jsp的设置,⽽且也⽆法执⾏
response.setCharacterEncoding()。例如:
如果同时采⽤了jsp输出和meta设置两种编码指定⽅式,则jsp指定的优先。因为jsp指定的直接体现在response中。
需要注意的是,apache有⼀个设置可以给⽆编码指定的⽹页指定编码,该指定等同于jsp的编码指定⽅式,所以会覆盖静态⽹页中的meta 指定。所以有⼈建议关闭该设置。
3.5.
4. form设置
当浏览器提交表单的时候,可以指定相应的编码。例如:
。⼀般不必不使⽤该设置,浏览器会直接使⽤⽹页的编码。
4. 系统软件
下⾯讨论⼏个相关的系统软件。
4.1. mysql数据库
很明显,要⽀持多语⾔,应该将数据库的编码设置成utf或者unicode,⽽utf更适合与存储。但是,如果中⽂数据中包含的英⽂字母很少,其实unicode更为适合。
数据库的编码可以通过mysql的配置⽂件设置,例如default-character-set=utf8。还可以在数据库链接URL中设置,例如:useUnicode=true&characterEncoding=UTF-8。注意这两者应该保持⼀致,在新的sql版本⾥,在数据库链接URL⾥可以不进⾏设置,但也不能是错误的设置。
4.2. apache
appache和编码有关的配置在f中,例如AddDefaultCharset UTF-8。如前所述,该功能会将所有静态页⾯的编码设置为UTF-8,最好关闭该功能。
另外,apache还有单独的模块来处理⽹页响应头,其中也可能对编码进⾏设置。
4.3. linux默认编码
这⾥所说的linux默认编码,是指运⾏时的环境变量。两个重要的环境变量是LC_ALL和LANG,默认编码会影响到java URLEncode的⾏为,下⾯有描述。
建议都设置为"zh_CN.UTF-8"。
4.4. 其它
为了⽀持中⽂⽂件名,linux在加载磁盘时应该指定字符集,例如:mount /dev/hda5 /mnt/hda5/ -t ntfs -o iocharset=gb2312。
另外,如前所述,使⽤GET⽅法提交的信息不⽀持request.setCharacterEncoding(),但可以通过tomcat的配置⽂件指定字符集,在tomcat的l⽂件中,形如:。这种⽅法将统⼀设置所有请求,⽽不能针对具体页⾯进⾏设置,也不⼀定和browser使⽤的编码相同,所以有时候并不是所期望的。
5. URL地址
URL地址中含有中⽂字符是很⿇烦的,前⾯描述过使⽤GET⽅法提交表单的情况,使⽤GET⽅法时,参数就是包含在URL中。
5.1. URL编码
对于URL中的⼀些特殊字符,浏览器会⾃动进⾏编码。这些字符除了"/?&"等外,还包括unicode字符,⽐如汉⼦。这时的编码⽐较特殊。
java语言使用的字符码集是IE有⼀个选项"总是使⽤UTF-8发送URL",当该选项有效时,IE将会对特殊字符进⾏UTF-8编码,同时进⾏URL编码。如果改选项⽆效,则使⽤默认编码"GBK",并且不进⾏URL编码。但是,对于URL后⾯的参数,则总是不进⾏编码,相当于UTF-8选项⽆效。⽐如"中⽂.html? a=中⽂",当UTF-8选项有效时,将发送链接"%e4��⽂.html?a=\x4e\x2d\x65\x87";⽽UTF-8选项⽆效时,将发送链
接"\x4e\x2d\x65\x87.html?a=\x4e\x2d\x65\x87"。注意后者前⾯的"中⽂"两个字只有4个字节,⽽前者却有18个字节,这主要时URL编码的原因。
当web server(tomcat)接收到该链接时,将会进⾏URL解码,即去掉"%",同时按照ISO8859-1编码(上⾯已经描述,可以使⽤URLEncoding来设置成其它编码)识别。上述例⼦的结果分别是"\ue4\ub8\uad\ue6\u96\u87.html?
a=\u4e\u2d\u65\u87"和"\u4e\u2d\u65\u87.html?a=\u4e\u2d\u65\u87",注意前者前⾯的"中⽂"两个字恢复成了6个字符。这⾥⽤"\u",表⽰是unicode。
所以,由于客户端设置的不同,相同的链接,在服务器上得到了不同结果。这个问题不少⼈都遇到,却没有很好的解决办法。所以有的⽹站会建议⽤户尝试关闭UTF-8选项。不过,下⾯会描述⼀个更好的处理办法。
5.2. rewrite
熟悉的⼈都知道,apache有⼀个功能强⼤的rewrite模块,这⾥不描述其功能。需要说明的是该模块会⾃动将URL解码(去除%),即完成上述web server(tomcat)的部分功能。有相关⽂档介绍说可以使⽤[NE]参数来关闭该功能,但我试验并未成功,可能是因为版本(我使⽤的是apache 2.0.54)问题。另外,当参数中含有"?& "等符号的时候,该功能将导致系统得不到正常结果。
rewrite本⾝似乎完全是采⽤字节处理的⽅式,⽽不考虑字符串的编码,所以不会带来编码问题。
5.3. de()
这是Java本⾝提供对的URL编码函数,完成的⼯作和上述UTF-8选项有效时浏览器所做的⼯作相似。值得说明的是,java已经不赞成不指定编码来使⽤该⽅法(deprecated)。应该在使⽤的时候增加编码指定。
当不指定编码的时候,该⽅法使⽤系统默认编码,这会导致软件运⾏结果得不确定。⽐如对于"中⽂",当系统默认编码为"gb2312"时,结果是"%4e-e�",⽽默认编码为"UTF-8",结果却是"%e4��⽂",后续程序将难以处理。另外,这⼉说的系统默认编码是由运⾏tomcat时的环境变量LC_ALL和LANG等决定的,曾经出现过tomcat重启后就出现乱码的问题,最后才郁闷的发现是因为修改修改了这两个环境变量。
建议统⼀指定为"UTF-8"编码,可能需要修改相应的程序。
6. 其它
下⾯描述⼀些和编码有关的其他问题。
6.1. SecureCRT
除了浏览器和控制台与编码有关外,⼀些客户端也很有关系。⽐如在使⽤SecureCRT连接linux时,应该让SecureCRT的显⽰编码(不同的session,可以有不同的编码设置)和linux的编码环境变量保持⼀致。否则看到的⼀些帮助信息,就可能是乱码。
另外,mysql有⾃⼰的编码设置,也应该保持和SecureCRT的显⽰编码⼀致。否则通过SecureCRT执⾏sql语句的时候,可能⽆法处理中⽂字符,查询结果也会出现乱码。
对于Utf-8⽂件,很多编辑器(⽐如记事本)会在⽂件开头增加三个不可见的标志字节,如果作为mysql的输⼊⽂件,则必须要去掉这三个字符。(⽤linux的vi保存可以去掉这三个字符)。⼀个有趣的现象是,在中⽂windows下,创建⼀个新txt⽂件,⽤记事本打开,输⼊"连通"两个字,保存,再打开,你会发现两个字没了,只留下⼀个⼩⿊点。
6.2. 过滤器
如果需要统⼀设置编码,则通过filter进⾏设置是个不错的选择。在filter class中,可以统⼀为需要的请求或者回应设置编码。参加上述setCharacterEncoding()。这个类apache已经给出了可以直接使⽤的例⼦SetCharacterEncodingFilter。
6.3. POST和GET
很明显,以POST提交信息时,URL有更好的可读性,⽽且可以⽅便的使⽤setCharacterEncoding()来处理字符集问题。但GET⽅法形成的URL能够更容易表达⽹页的实际内容,也能够⽤于收藏。
从统⼀的⾓度考虑问题,建议采⽤GET⽅法,这要求在程序中获得参数是进⾏特殊处理,⽽⽆法使⽤setCharacterEncoding()的便利,如果不考虑rewrite,就不存在IE的UTF-8问题,可以考虑通过设置URIEncoding来⽅便获取URL中的参数。
6.4. 简繁体编码转换
GBK同时包含简体和繁体编码,也就是说同⼀个字,由于编码不同,在GBK编码下属于两个字。有时候,为了正确取得完整的结果,应该将繁体和简体进⾏统⼀。可以考虑将UTF、GBK中的所有繁体字,转换为相应的简体字,BIG5编码的数据,也应该转化成相应的简体字。当然,仍旧以UTF编码存储。
例如,对于"语⾔ 語⾔",⽤UTF表⽰为"\xE8\xAF\xAD\xE8\xA8\x80 \xE8\xAA\x9E\xE8\xA8\x80",进⾏简繁体编码转换后应该是两个相同的 "\xE8\xAF\xAD\xE8\xA8\x80>"。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论