1、1. 概述本文主要包括以下几个方面:编码基本知识,java,系统软件,url,工具软件等。在下面的描述中,将以“中文“两个字为例,经查表可以知道其 GB2312 编码是“d6d0 cec4“,Unicode 编码为“4e2d 6587“,UTF 编码就是“e4b8ad e69687“。注意,这两个字没有iso8859-1 编码,但可以用 iso8859-1 编码来“表示“。2. 编码基本知识最早的编码是 iso8859-1,和 ascii 编码相似。但为了方便表示各种各样的语言,逐渐出现了很多标准编码,重要的有如下几个。2.1. iso8859-1属于单字节编码,最多能表示的字符范围是 0-2
2、55,应用于英文系列。比如,字母 a 的编码为 0x61=97。很明显,iso8859-1 编码表示的字符范围很窄,无法表示中文字符。但是,由于是单字节编码,和计算机最基础的表示单位一致,所以很多时候,仍旧使用 iso8859-1 编码来表示。而且在很多协议上,默认使用该编码。比如,虽然“中文“两个字不存在 iso8859-1 编码,以gb2312 编码为例,应该是“d6d0 cec4“两个字符,使用 iso8859-1 编码的时候则将它拆开为4 个字节来表示:“d6 d0 ce c4“(事实上,在进行存储的时候,也是以字节为单位处理的) 。而如果是 UTF 编码,则是 6 个字节“e4 b8
3、 ad e6 96 87“。很明显,这种表示方法还需要以另一种编码为基础。2.2. GB2312/GBK这就是汉子的国标码,专门用来表示汉字,是双字节编码,而英文字母和 iso8859-1 一致(兼容 iso8859-1 编码) 。其中 gbk 编码能够用来同时表示繁体字和简体字,而 gb2312 只能表示简体字,gbk 是兼容 gb2312 编码的。2.3. unicode这是最统一的编码,可以用来表示所有语言的字符,而且是定长双字节(也有四字节的)编码,包括英文字母在内。所以可以说它是不兼容 iso8859-1 编码的,也不兼容任何编码。不过,相对于 iso8859-1 编码来说,unio
4、code 编码只是在前面增加了一个 0 字节,比如字母a 为“00 61“。需要说明的是,定长编码便于计算机处理(注意 GB2312/GBK 不是定长编码) ,而 unicode 又可以用来表示所有字符,所以在很多软件内部是使用 unicode 编码来处理的,比如 java。2.4. UTF考虑到 unicode 编码不兼容 iso8859-1 编码,而且容易占用更多的空间:因为对于英文字母,unicode 也需要两个字节来表示。所以 unicode 不便于传输和存储。因此而产生了 utf 编码,utf 编码兼容 iso8859-1 编码,同时也可以用来表示所有语言的字符,不过,utf 编码是
5、不定长编码,每一个字符的长度从 1-6 个字节不等。另外,utf 编码自带简单的校验功能。一般来讲,英文字母都是用一个字节表示,而汉字使用三个字节。注意,虽然说 utf 是为了使用更少的空间而使用的,但那只是相对于 unicode 编码来说,如果已经知道是汉字,则使用 GB2312/GBK 无疑是最节省的。不过另一方面,值得说明的是,虽然 utf 编码对汉字使用 3 个字节,但即使对于汉字网页,utf 编码也会比 unicode 编码节省,因为网页中包含了很多的英文字符。3. java 对字符的处理在 java 应用软件中,会有多处涉及到字符集编码,有些地方需要进行正确的设置,有些地方需要进行
6、一定程度的处理。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.
7、2. new String(charset)这是 java 字符串处理的另一个标准函数,和上一个函数的作用相反,将字节数组按照charset 编码进行组合识别,最后转换为 unicode 存储。参考上述 getBytes 的例子,“gbk“ 和“utf8“都可以得出正确的结果“4e2d 6587“,但 iso8859-1 最后变成了“003f 003f“(两个问号) 。因为 utf8 可以用来表示/编码所有字符,所以 new String( str.getBytes( “utf8“ ), “utf8“ ) = str,即完全可逆。3.3. setCharacterEncoding()该函数用来
8、设置 http 请求或者相应的编码。对于 request,是指提交内容的编码,指定后可以通过 getParameter()则直接获得正确的字符串,如果不指定,则默认使用 iso8859-1 编码,需要进一步处理。参见下述“表单输入“。值得注意的是在执行 setCharacterEncoding()之前,不能执行任何 getParameter()。java doc 上说明:This method must be called prior to reading request parameters or reading input using getReader()。而且,该指定只对 POST 方
9、法有效,对 GET 方法无效。分析原因,应该是在执行第一个 getParameter()的时候,java 将会按照编码分析所有的提交内容,而后续的 getParameter()不再进行分析,所以 setCharacterEncoding()无效。而对于 GET 方法提交表单是,提交的内容在 URL 中,一开始就已经按照编码分析所有的提交内容,setCharacterEncoding()自然就无效。对于 response,则是指定输出内容的编码,同时,该设置会传递给浏览器,告诉浏览器输出内容所采用的编码。3.4. 处理过程下面分析两个有代表性的例子,说明 java 对编码有关问题的处理方法。3.
10、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,可以在表
11、单中指定提交内容时使用的字符集,否则会使用页面指定的编码。而如果在 url 中直接用?的方式输入参数,则其编码往往是操作系统本身的编码,因为这时和页面无关。上述仍旧以 gbk 编码为例。l Web server 接收到的是字节流,默认时(getParameter)会以 iso8859-1 编码处理之,结果是不正确的,所以需要进行处理。但如果预先设置了编码(通过 request. setCharacterEncoding ()) ,则能够直接获取到正确的结果。l 在页面中指定编码是个好习惯,否则可能失去控制,无法指定正确的编码。3.4.2. 文件编译假设文件是 gbk 编码保存的,而编译有两种编
12、码选择: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 0
13、00ce 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 下运行/编译,则
14、会出现问题。所以需要在jsp 文件中用 pageEncoding 指定编码。l Java 编译的时候会转换成统一的 unicode 编码处理,最后保存的时候再转换为 utf 编码。l 当系统输出字符的时候,会按指定编码输出,对于中文 windows 下,System.out 将使用gbk 编码,而对于 response(浏览器) ,则使用 jsp 文件头指定的 contentType,或者可以直接为 response 指定编码。同时,会告诉 browser 网页的编码。如果未指定,则会使用iso8859-1 编码。对于中文,应该为 browser 指定输出字符串的编码。l browser 显示
15、网页的时候,首先使用 response 中指定的编码(jsp 文件头指定的contentType 最终也反映在 response 上) ,如果未指定,则会使用网页中 meta 项指定中的contentType。3.5. 几处设置对于 web 应用程序,和编码有关的设置或者函数如下。3.5.1. jsp 编译指定文件的存储编码,很明显,该设置应该置于文件的开头。例如:。另外,对于一般class 文件,可以在编译的时候指定编码。3.5.2. jsp 输出指定文件输出到 browser 是使用的编码,该设置也应该置于文件的开头。例如:。该设置和response.setCharacterEncodin
16、g(“GBK“)等效。3.5.3. meta 设置指定网页使用的编码,该设置对静态网页尤其有作用。因为静态网页无法采用 jsp 的设置,而且也无法执行 response.setCharacterEncoding()。例如:如果同时采用了 jsp 输出和 meta 设置两种编码指定方式,则 jsp 指定的优先。因为 jsp 指定的直接体现在 response 中。需要注意的是,apache 有一个设置可以给无编码指定的网页指定编码,该指定等同于 jsp的编码指定方式,所以会覆盖静态网页中的 meta 指定。所以有人建议关闭该设置。3.5.4. form 设置当浏览器提交表单的时候,可以指定相应的
17、编码。例如:。一般不必不使用该设置,浏览器会直接使用网页的编码。4. 系统软件下面讨论几个相关的系统软件。4.1. mysql 数据库很明显,要支持多语言,应该将数据库的编码设置成 utf 或者 unicode,而 utf 更适合与存储。但是,如果中文数据中包含的英文字母很少,其实 unicode 更为适合。数据库的编码可以通过 mysql 的配置文件设置,例如 default-character-set=utf8。还可以在数据库链接 URL 中设置,例如: useUnicode=truefor(int i=0;ibyte lByte=bi+;/to fast operation, + now
18、, ready for the following for(;)if(lByte=0) continue;/=0 is normal asciiif(lByte(byte)0xfd) return false;int lCount=lByte(byte)0xfc?5:lByte(byte)0xf8?4:lByte(byte)0xf0?3:lByte(byte)0xe0?2:1;if(i+lCountlLen) return false;for(int j=0;j=(byte)0xc0) return false;return true;相应地,一个使用上述方法的例子如下:public stat
19、ic String getUrlParam(String aStr,String aDefaultCharset)throws UnsupportedEncodingExceptionif(aStr=null) return null;byte lBytes=aStr.getBytes(“ISO-8859-1“);return new String(lBytes,StringUtil.isValidUtf8(lBytes)?“utf8“:aDefaultCharset);不过,该方法也存在缺陷,如下两方面:l 没有包括对用户默认编码的识别,这可以根据请求信息的语言来判断,但不一定正确,因为我们
20、有时候也会输入一些韩文,或者其他文字。l 可能会错误判断 UTF-8 字符,一个例子是“学习“两个字,其 GBK 编码是“ xd1xa7xcfxb0“,如果使用上述 isValidUtf8 方法判断,将返回 true。可以考虑使用更严格的判断方法,不过估计效果不大。有一个例子可以证明 google 也遇到了上述问题,而且也采用了和上述相似的处理方法,比如,如果在地址栏中输入“http:/ 将无法正确识别,而其他汉字一般能够正常识别。最后,应该补充说明一下,如果不使用 rewrite 规则,或者通过表单提交数据,其实并不一定会遇到上述问题,因为这时可以在提交数据时指定希望的编码。另外,中文文件名
21、确实会带来问题,应该谨慎使用。6. 其它下面描述一些和编码有关的其他问题。6.1. SecureCRT除了浏览器和控制台与编码有关外,一些客户端也很有关系。比如在使用 SecureCRT 连接linux 时,应该让 SecureCRT 的显示编码(不同的 session,可以有不同的编码设置)和linux 的编码环境变量保持一致。否则看到的一些帮助信息,就可能是乱码。另外,mysql 有自己的编码设置,也应该保持和 SecureCRT 的显示编码一致。否则通过SecureCRT 执行 sql 语句的时候,可能无法处理中文字符,查询结果也会出现乱码。对于 Utf-8 文件,很多编辑器(比如记事本
22、)会在文件开头增加三个不可见的标志字节,如果作为 mysql 的输入文件,则必须要去掉这三个字符。 (用 linux 的 vi 保存可以去掉这三个字符) 。一个有趣的现象是,在中文 windows 下,创建一个新 txt 文件,用记事本打开,输入“连通“两个字,保存,再打开,你会发现两个字没了,只留下一个小黑点。6.2. 过滤器如果需要统一设置编码,则通过 filter 进行设置是个不错的选择。在 filter class 中,可以统一为需要的请求或者回应设置编码。参加上述 setCharacterEncoding()。这个类apache 已经给出了可以直接使用的例子 SetCharacter
23、EncodingFilter。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 表示为“xE8xAFxADxE8xA8x80 xE8xAAx9ExE8xA8x80“,进行简繁体编码转换后应该是两个相同的 “xE8xAFxADxE8xA8x80“。