Java 编码格式研究

来源:互联网 发布:cms团队分销系统 编辑:程序博客网 时间:2024/05/23 13:49
 java中的String类是按照unicode进行编码的,当使用String(byte[] bytes, String encoding)构造字符串时,encoding所指的是bytes中的数据是按照那种方式编码的,而不是最后产生的String是什么编码方式,换句话说,是让系统把bytes中的数据由encoding编码方式转换成unicode编码。如果不指明,bytes的编码方式将由jdk根据操作系统决定。

        当我们从文件中读数据时,最好使用InputStream方式,然后采用String(byte[] bytes, String encoding)指明文件的编码方式。不要使用Reader方式,因为Reader方式会自动根据jdk指明的编码方式把文件内容转换成unicode编码。

        当我们从数据库中读文本数据时,采用ResultSet.getBytes()方法取得字节数组,同样采用带编码方式的字符串构造方法即可。

ResultSet rs;
bytep[] bytes = rs.getBytes();
String str = new String(bytes, "gb2312");

不要采取下面的步骤。

ResultSet rs;
String str = rs.getString();
str = new String(str.getBytes("iso8859-1"), "gb2312");

        这种编码转换方式效率底。之所以这么做的原因是,ResultSet在getString()方法执行时,默认数据库里的数据编码方式为iso8859-1。系统会把数据依照iso8859-1的编码方式转换成unicode。使用str.getBytes("iso8859-1")把数据还原,然后利用new String(bytes, "gb2312")把数据从gb2312转换成unicode,中间多了好多步骤。

        从HttpRequest中读参数时,利用reqeust.setCharacterEncoding()方法设置编码方式,读出的内容就是正确的了。

 几种编码方式:

2.1. iso8859-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 (定长编码,Java里面都是unicode编码)

这是最统一的编码,可以用来表示所有语言的字符,而且是定长双字节(也有四字节的)编码,包括英文字母在内。所以可以说它是不兼容iso8859-1编码的,也不兼容任何编码。不过,相对于iso8859-1编码来说,uniocode编码只是在前面增加了一个0字节,比如字母'a'为"00 61"。 

需要说明的是,定长编码便于计算机处理(注意GB2312/GBK不是定长编码),而unicode又可以用来表示所有字符,所以在很多软件内部是使用unicode编码来处理的,比如java。 

2.4. UTF 

考虑到unicode编码不兼容iso8859-1编码,而且容易占用更多的空间:因为对于英文字母,unicode也需要两个字节来表示。所以unicode不便于传输和存储。因此而产生了utf编码,utf编码兼容iso8859-1编码,同时也可以用来表示所有语言的字符,不过,utf编码是不定长编码,每一个字符的长度从1-6个字节不等。另外,utf编码自带简单的校验功能。一般来讲,英文字母都是用一个字节表示,而汉字使用三个字节。 

注意,虽然说utf是为了使用更少的空间而使用的,但那只是相对于unicode编码来说,如果已经知道是汉字,则使用GB2312/GBK无疑是最节省的。不过另一方面,值得说明的是,虽然utf编码对汉字使用3个字节,但即使对于汉字网页,utf编码也会比unicode编码节省,因为网页中包含了很多的英文字符。 

UTF: Unicode/UCS Transformation Format

  • UTF-8, 8bit编码, ASCII不作变换, 其他字符做变长编码, 每个字符1-3 byte. 通常作为外码. 有以下优点:
    * 与CPU字节顺序无关, 可以在不同平台之间交流
    * 容错能力高, 任何一个字节损坏后, 最多只会导致一个编码码位损失, 不会链锁错误(如GB码错一个字节就会整行乱码)
  • UTF-16, 16bit编码, 是变长码, 大致相当于20位编码, 值在0到0x10FFFF之间, 基本上就是unicode编码的实现. 它是变长码, 与CPU字序有关, 但因为最省空间, 常作为网络传输的外码.
    UTF-16是unicode的preferred encoding.
  • UTF-32, 仅使用了unicode范围(0到0x10FFFF)的32位编码, 相当于UCS-4的子集.

    UTF与unicode的关系:

    Unicode是一个字符集, 可以看作为内码.
    而UTF是一种编码方式, 它的出现是因为unicode不适宜在某些场合直接传输和处理. UTF-16直接就是unicode编码, 没有变换, 但它包含了0x00在编码内, 头256字节码的第一个byte都是0x00, 在操作系统(C语言)中有特殊意义, 会引起问题. 采用UTF-8编码对unicode的直接编码作些变换可以避免这问题, 并带来一些优点.

    中国国标编码:
  • GB 13000: 完全等同于ISO 10646-1/Unicode 2.1, 今后也将随ISO 10646/Unicode的标准更改而同步更改.
  • GBK: 对GB2312的扩充, 以容纳GB2312字符集范围以外的Unicode 2.1的统一汉字部分, 并且增加了部分unicode中没有的字符.
  • GB 18030-2000: 基于GB 13000, 作为Unicode 3.0的GBK扩展版本, 覆盖了所有unicode编码, 地位等同于UTF-8, UTF-16, 是一种unicode编码形式. 变长编码, 用单字节/双字节/4字节对字符编码. GB18030向下兼容GB2312/GBK.
    GB 18030是中国所有非手持/嵌入式计算机系统的强制实施标准.
  • 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( str.getBytes( "utf8" ), "utf8" ) === str,即完全可逆。 

    3.3. setCharacterEncoding() 

    该函数用来设置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方法有效,对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编译 

    指定文件的存储编码,很明显,该设置应该置于文件的开头。例如:<%@page pageEncoding="GBK"%>。另外,对于一般class文件,可以在编译的时候指定编码。 

    3.5.2. jsp输出 

    指定文件输出到browser是使用的编码,该设置也应该置于文件的开头。例如:<%@ page contentType="text/html; charset= GBK" %>。该设置和response.setCharacterEncoding("GBK")等效。 

    3.5.3. meta设置 

    指定网页使用的编码,该设置对静态网页尤其有作用。因为静态网页无法采用jsp的设置,而且也无法执行response.setCharacterEncoding()。例如:<META http-equiv="Content-Type" content="text/html; charset=GBK" /> 

    如果同时采用了jsp输出和meta设置两种编码指定方式,则jsp指定的优先。因为jsp指定的直接体现在response中。 

    需要注意的是,apache有一个设置可以给无编码指定的网页指定编码,该指定等同于jsp的编码指定方式,所以会覆盖静态网页中的meta指定。所以有人建议关闭该设置。 

    3.5.4. form设置 

    当浏览器提交表单的时候,可以指定相应的编码。例如:<form accept-charset= "gb2312">。一般不必不使用该设置,浏览器会直接使用网页的编码。 

    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和编码有关的配置在httpd.conf中,例如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的server.xml文件中,形如:<Connector ... URIEncoding="GBK"/>。这种方法将统一设置所有请求,而不能针对具体页面进行设置,也不一定和browser使用的编码相同,所以有时候并不是所期望的。 

    5. URL地址 

    URL地址中含有中文字符是很麻烦的,前面描述过使用GET方法提交表单的情况,使用GET方法时,参数就是包含在URL中。 

    5.1. URL编码 

    对于URL中的一些特殊字符,浏览器会自动进行编码。这些字符除了"/?&"等外,还包括unicode字符,比如汉子。这时的编码比较特殊。 

    IE有一个选项"总是使用UTF-8发送URL",当该选项有效时,IE将会对特殊字符进行UTF-8编码,同时进行URL编码。如果改选项无效,则使用默认编码"GBK",并且不进行URL编码。但是,对于URL后面的参数,则总是不进行编码,相当于UTF-8选项无效。比如"中文.html?a=中文",当UTF-8选项有效时,将发送链接"%e4%b8%ad%e6%96%87.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. URLEncode.encode() 

    这是Java本身提供对的URL编码函数,完成的工作和上述UTF-8选项有效时浏览器所做的工作相似。值得说明的是,java已经不赞成不指定编码来使用该方法(deprecated)。应该在使用的时候增加编码指定。 

    当不指定编码的时候,该方法使用系统默认编码,这会导致软件运行结果得不确定。比如对于"中文",当系统默认编码为"gb2312"时,结果是"%4e%2d%65%87",而默认编码为"UTF-8",结果却是"%e4%b8%ad%e6%96%87",后续程序将难以处理。另外,这儿说的系统默认编码是由运行tomcat时的环境变量LC_ALL和LANG等决定的,曾经出现过tomcat重启后就出现乱码的问题,最后才郁闷的发现是因为修改修改了这两个环境变量。 

    建议统一指定为"UTF-8"编码,可能需要修改相应的程序。 

    5.4. 一个解决方案 

    上面说起过,因为浏览器设置的不同,对于同一个链接,web server收到的是不同内容,而软件系统有无法知道这中间的区别,所以这一协议目前还存在缺陷。 

    针对具体问题,不应该侥幸认为所有客户的IE设置都是UTF-8有效的,也不应该粗暴的建议用户修改IE设置,要知道,用户不可能去记住每一个web server的设置。所以,接下来的解决办法就只能是让自己的程序多一点智能:根据内容来分析编码是否UTF-8。 

    比较幸运的是UTF-8编码相当有规律,所以可以通过分析传输过来的链接内容,来判断是否是正确的UTF-8字符,如果是,则以UTF-8处理之,如果不是,则使用客户默认编码(比如"GBK"),下面是一个判断是否UTF-8的例子,如果你了解相应规律,就容易理解。 

    public static boolean isValidUtf8(byte[] b,int aMaxCount){ 

           int lLen=b.length,lCharCount=0; 

           for(int i=0;i<lLen && lCharCount<aMaxCount;++lCharCount){ 

                  byte lByte=b[i++];//to fast operation, ++ now, ready for the following for(;;) 

                  if(lByte>=0) continue;//>=0 is normal ascii 

                  if(lByte<(byte)0xc0 || 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+lCount>lLen) return false; 

                  for(int j=0;j<lCount;++j,++i) if(b[i]>=(byte)0xc0) return false; 

           } 

           return true; 



    相应地,一个使用上述方法的例子如下: 

    public static String getUrlParam(String aStr,String aDefaultCharset) 

    throws UnsupportedEncodingException{ 

           if(aStr==null) return null; 

           byte[] lBytes=aStr.getBytes("ISO-8859-1"); 

           return new String(lBytes,StringUtil.isValidUtf8(lBytes)?"utf8":aDefaultCharset); 



    不过,该方法也存在缺陷,如下两方面: 

    l 没有包括对用户默认编码的识别,这可以根据请求信息的语言来判断,但不一定正确,因为我们有时候也会输入一些韩文,或者其他文字。 

    l 可能会错误判断UTF-8字符,一个例子是"学习"两个字,其GBK编码是" /xd1/xa7/xcf/xb0",如果使用上述isValidUtf8方法判断,将返回true。可以考虑使用更严格的判断方法,不过估计效果不大。 

    有一个例子可以证明google也遇到了上述问题,而且也采用了和上述相似的处理方法,比如,如果在地址栏中输入"http://www.google.com/search?hl=zh-CN&newwindow=1&q=学习",google将无法正确识别,而其他汉字一般能够正常识别。 

    最后,应该补充说明一下,如果不使用rewrite规则,或者通过表单提交数据,其实并不一定会遇到上述问题,因为这时可以在提交数据时指定希望的编码。另外,中文文件名确实会带来问题,应该谨慎使用。 

    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>"。 

    Java字符编码转换过程说明

     

    javacn_fig1.gif 

    常见问题

     

    javacn_fig2.gif 

    JVM

    JVM启动后,JVM会设置一些系统属性以表明JVM的缺省区域。

    user.language,user.region,file.encoding等。 可以使用System.getProperties()详细查看所有的系统属性。

     

    如在英文操作系统(UNIX)下,可以使用如下属性定义强制指定JVM为中文环境 -Dclient.encoding.override=GBK -Dfile.encoding=GBK -Duser.language=zh -Duser.region=CN

     

    .java-->.class编译

    说明:一般javac根据当前os区域设置,自动决定源文件的编码.可以通过-encoding强制指定.

     

    错误可能:

    1 gbk编码源文件在英文环境下编译,javac不能正确转换.曾见于java/jsp在英文unix. 检测方法:/u4e00格式的汉字,绕开javac编码,再在jvm,将汉字作为int打印,看值是否相等;或直接以UTF-8编码打开.class文件,看看常量字符串是否正确保存汉字。

     

    文件读写

    外部数据如文件经过读写和转换两个步骤,转为jvm所使用字符。InputStream/OutputStream用于读写原始外部数据,Reader/Writer执行读写和转换两个步骤。

     

    1 文件读写转换由java.io.Reader/Writer执行;输入输出流 InputStream/OutputStream  处理汉字不合适,应该首选使用Reader/Writer,如 FileReader/FileWriter

     

    2 FileReader/FileWriter使用JVM当前编码读写文件.如果有其它编码格式,使用InputStreamReader/OutputStreamWriter

     

    3 PrintStream有点特殊,它自动使用jvm缺省编码进行转换。

     

     

    读取.properties文件

    .propeties文件由Properties类以iso8859-1编码读取,因此不能在其中直接写汉字,需要使用JDK native2ascii工具转换汉字为/uXXXX格式。命令行:native2ascii –encoding GBK inputfile outputfile

     

    读取XML文件

    1 XML文件读写同于文件读写,但应注意确保XML头中声明如<? xml version=”1.0” encoding=”gb2312” ?>与文件编码保持一致。

     

    2 javax.xml.SAXParser类接受InputStream作为输入参数,对于Reader,需要用org.xml.sax.InputSource包装一下,再给SAXParser

     

    3 对于UTF-8编码 XML,注意防止编辑器自动加上/uFFFE BOM, xml parser会报告content is not allowed in prolog

     

     

    字节数组

    1 使用 new String(byteArray,encoding)   String.getBytes(encoding)  在字节数组和字符串之间进行转换

     

    也可以用ByteArrayInputStream/ByteArrayOutputStream转为流后再用InputStreamReader/OutputStreamWriter转换。

     

    错误编码的字符串(iso8859-1转码gbk)

    如果我们得到的字符串是由错误的转码方式产生的,例如:对于gbk中文,由iso8859-1方式转换,此时如果用调试器看到的字符串一般是 的样子,长度一般为文本的字节长度,而非汉字个数。

     

    可以采用如下方式转为正确的中文:

    text = new String( text.getBytes(“iso8859-1”),”gbk”);

     

    JDBC

    转换过程由JDBC Driver执行,取决于各JDBC数据库实现。对此经验尚积累不够。

     

    1 对于ORACLE数据库,需要数据库创建时指定编码方式为gbk,否则会出现汉字转码错误

    2 对于 SQL Server 2000 ,最好以nvarchar/nchar类型存放文本,即不存在中文/编码转换问题。

    3 连接 Mysql,将 connectionString 设置成 encoding gb2312

     String connectionString  = "jdbc:mysql://localhost/test?useUnicode=true&characterEncoding=gb2312";

     

    WEB/Servlet/JSP

    1 对于JSP,确定头部加上 <%@ page contentType="text/html;charset=gb2312"%>这样的标签。

    2 对于Servlet,确定 设置setContentType (“text/html; charset=gb2312”),以上两条用于使得输出汉字没有问题。

    3 为输出HTML head中加一个 <meta http-equiv="Content-Type" content="text/html; charset=gb2312"> ,让浏览器正确确定HTML编码。

     

    4 Web应用加一个Filter,确保每个Request明确调用setCharacterEncoding方法,让输入汉字能够正确解析。

     

     

    import java.io.IOException;

    import javax.servlet.Filter;

    import javax.servlet.FilterChain;

    import javax.servlet.FilterConfig;

    import javax.servlet.ServletException;

    import javax.servlet.ServletRequest;

    import javax.servlet.ServletResponse;

    import javax.servlet.UnavailableException;

    import javax.servlet.http.HttpServletRequest;

     

    /**

     * Example filter that sets the character encoding to be used in parsing the

     * incoming request

     */

    public class SetCharacterEncodingFilter

        implements Filter {

      public SetCharacterEncodingFilter()

      {}

      protected boolean debug = false;

      protected String encoding = null;

      protected FilterConfig filterConfig = null;

      public void destroy() {

        this.encoding = null;

        this.filterConfig = null;

      }

     

      public void doFilter(ServletRequest request, ServletResponse response,

                           FilterChain chain) throws IOException, ServletException {

    //    if (request.getCharacterEncoding() == null)

    //    {

    //      String encoding = getEncoding();

    //      if (encoding != null)

    //        request.setCharacterEncoding(encoding);

    //

    //    }

          request.setCharacterEncoding(encoding);

          if ( debug ){

            System.out.println( ((HttpServletRequest)request).getRequestURI()+"setted to "+encoding );

          }

        chain.doFilter(request, response);

      }

     

      public void init(FilterConfig filterConfig) throws ServletException {

        this.filterConfig = filterConfig;

        this.encoding = filterConfig.getInitParameter("encoding");

        this.debug = "true".equalsIgnoreCase( filterConfig.getInitParameter("debug") );

      }

     

      protected String getEncoding() {

        return (this.encoding);

      }

    }

     

     

    web.xml中加入:

     

      <filter>

        <filter-name>LocalEncodingFilter</filter-name>

        <display-name>LocalEncodingFilter</display-name>

        <filter-class>com.ccb.ectipmanager.request.SetCharacterEncodingFilter</filter-class>

        <init-param>

          <param-name>encoding</param-name>

          <param-value>gb2312</param-value>

        </init-param>

        <init-param>

          <param-name>debug</param-name>

          <param-value>false</param-value>

        </init-param>

      </filter>

     

       <filter-mapping>

        <filter-name>LocalEncodingFilter</filter-name>

        <url-pattern>/*</url-pattern>

      </filter-mapping>

     

    5 用于Weblogicvedor-specific):

    其一:web.xml里加上如下脚本:

    <context-param>

      <param-name>weblogic.httpd.inputCharset./*</param-name>

      <param-value>GBK</param-value>

    </context-param>

    其二(可选)在weblogic.xml里加上如下脚本:

    <charset-params>

      <input-charset>

          <resource-path>/*</resource-path>

          <java-charset-name>GBK</java-charset-name>

      </input-charset>

    </charset-params>

     

    SWING/AWT/SWT

    对于SWING/AWTJava会有些缺省字体如Dialog/San Serif,这些字体到系统真实字体的映射在$JRE_HOME/lib/font.properties.XXX文件中指定。排除字体显示问题时,首先需要确定JVM的区域为zh_CN,这样font.properties.zh_CN文件才会发生作用。对于 font.properties.zh_CN , 需要检查是否映射缺省字体到中文字体如宋体。

     

    Swing中,Java自行解释TTF字体,渲染显示;对于AWT,SWT显示部分交由操作系统。首先需要确定系统装有中文字体。

     

    1 汉字显示为,一般为显示字体没有使用中文字体,因为Java对于当前字体显示不了的字符,不会像Windows一样再采用缺省字体显示。

    2 部分不常见汉字不能显示,一般为显示字库中汉字不全,可以换另外的中文字体试试。

    3 对于AWT/SWT,首先确定JVM运行环境的区域设置为中文,因为此处设计JVM与操作系统api调用的转换问题,再检查其它问题。

     

    JNI

    JNIjstringUTF-8编码给我们,需要我们自行转为本地编码。对于Windows,可以采用WideCharToMultiByte/MultiByteToWideChar函数进行转换,对于Unix,可以采用iconv库。

     

    这里从SUN jdk 1.4 源代码中找到一段使用jvm String 对象的getBytes的转换方式,相对简单和跨平台,不需要第三方库,但速度稍慢。函数原型如下:

     

    /* Convert between Java strings and i18n C strings */

    JNIEXPORT jstring

    NewStringPlatform(JNIEnv *env, const char *str);

     

    JNIEXPORT const char *

    GetStringPlatformChars(JNIEnv *env, jstring jstr, jboolean *isCopy);

     

    JNIEXPORT jstring JNICALL

    JNU_NewStringPlatform(JNIEnv *env, const char *str);

     

    JNIEXPORT const char * JNICALL

    JNU_GetStringPlatformChars(JNIEnv *env, jstring jstr, jboolean *isCopy);

     

    JNIEXPORT void JNICALL

    JNU_ReleaseStringPlatformChars(JNIEnv *env, jstring jstr, const char *str);

     

     

    附件jni_util.h,jni_util.c

     

     

    TUXEDO/JOLT

    JOLT对于传递的字符串需要用如下进行转码

    new String(ls_tt.getBytes("GBK"),"iso8859-1")

     

    对于返回的字符串

    new String(error_message.getBytes("iso8859-1"),"GBK");

    jolt 的系统属性 bea.jolt.encoding不应该设置,如果设置,JSH会报告说错误的协议.

     

     

    JDK1.4/1.5新增部分

    字符集相关类(Charset/CharsetEncoder/CharsetDecoder)

    jdk1.4开始,对字符集的支持在java.nio.charset包中实现。

     

    常用功能:

    1 列出jvm所支持字符集:Charset.availableCharsets()

    2 能否对看某个Unicode字符编码,CharsetEncoder.canEncode()

     

    Unicode Surrogate/CJK EXT B

    Unicode 范围一般所用为/U0000-/UFFFF范围,jvm使用1char就可以表示,对于CJK EXT B区汉字,范围大于/U20000,则需要采用2char方能表示,此即Unicode Surrogate。这2char的值范围 落在Character.SURROGATE 区域内,用Character.getType()来判断。

     

    jdk 1.4尚不能在Swing中正确处理surrogate区的Unicode字符,jdk1.5可以。对于CJK EXT B区汉字,目前可以使用的字库为宋体-方正超大字符集”,Office安装。

     

    常见问题

    JVM下,用System.out.println不能正确打印中文,显示为???

    System.out.printlnPrintStream,它采用jvm缺省字符集进行转码工作,如果jvm的缺省字符集为iso8859-1,则中文显示会有问题。此问题常见于Unix下,jvm的区域没有明确指定的情况。

     

    在英文UNIX环境下,System.out.println能够正确打印汉字,但是内部处理错误

    可能是汉字在输入转换时,就没有正确转码:

    gbk文本à(iso8859-1转码)àjvm char(iso8859-1编码汉字)à (iso8859-1转码)à输出。

    gbk汉字经过两次错误转码,原封不动的被传递到输出,但是在jvm中,并未以正确的unicode编码表示,而是以一个汉字字节一个char的方式表示,从而导致此类错误。

     

     

    GB2312-80GBKGB18030-2000 汉字字符集

     

    GB2312-80 是在国内计算机汉字信息技术发展初始阶段制定的,其中包含了大部分常用的一、二级汉字,和 9 区的符号。该字符集是几乎所有的中文系统和国际化的软件都支持的中文字符集,这也是最基本的中文字符集。其编码范围是高位0xa10xfe,低位也是 0xa1-0xfe;汉字从 0xb0a1 开始,结束于 0xf7fe

     

    GBK GB2312-80 的扩展,是向上兼容的。它包含了 20902 个汉字,其编码范围是 0x8140-0xfefe,剔除高位 0x80 的字位。其所有字符都可以一对一映射到 Unicode 2.0,也就是说 JAVA 实际上提供了 GBK 字符集的支持。这是现阶段 Windows 和其它一些中文操作系统的缺省字符集,但并不是所有的国际化软件都支持该字符集,感觉是他们并不完全知道 GBK 是怎么回事。值得注意的是它不是国家标准,而只是规范。随着 GB18030-2000国标的发布,它将在不久的将来完成它的历史使命。

     

    GB18030-2000(GBK2K) GBK 的基础上进一步扩展了汉字,增加了藏、蒙等少数民族的字形。GBK2K 从根本上解决了字位不够,字形不足的问题。它有几个特点,

     

    它并没有确定所有的字形,只是规定了编码范围,留待以后扩充。

    编码是变长的,其二字节部分与 GBK 兼容;四字节部分是扩充的字形、字位,其编码范围是首字节 0x81-0xfe、二字节0x30-0x39、三字节 0x81-0xfe、四字节0x30-0x39

     

    UTF-8/UTF-16/UTF-32

     

    UTF,即Unicode Transformer Format,是Unicode代码点(code point)的实际表示方式,按其基本长度所用位数分为UTF-8/16/32。它也可以认为是一种特殊的外部数据编码,但能够与Unicode代码点做一一对应。

     

    UTF-8是变长编码,每个Unicode代码点按照不同范围,可以有1-3字节的不同长度。

    UTF-16长度相对固定,只要不处理大于/U200000范围的字符,每个Unicode代码点使用16位即2字节表示,超出部分使用两个UTF-164字节表示。按照高低位字节顺序,又分为UTF-16BE/UTF-16LE

    UTF-32长度始终固定,每个Unicode代码点使用32位即4字节表示。按照高低位字节顺序,又分为UTF-32BE/UTF-32LE

     

    UTF编码有个优点,即尽管编码字节数不等,但是不像gb2312/gbk编码一样,需要从文本开始寻找,才能正确对汉字进行定位。在UTF编码下,根据相对固定的算法,从当前位置就能够知道当前字节是否是一个代码点的开始还是结束,从而相对简单的进行字符定位。不过定位问题最简单的还是UTF-32,它根本不需要进行字符定位,但是相对的大小也增加不少。

     

     

    关于GCJ JVM

    GCJ并未完全依照sun jdk的做法,对于区域和编码问题考虑尚不够周全。GCJ启动时,区域始终设为en_US,编码也缺省为iso8859-1。但是可以用Reader/Writer做正确编码转换。

     

     

    GB2312,UTF-8,Unicode

    ===========================================================

    Unicode、Unicode-Bigendin 两个字节, 前面都有辨别码,分别是FF EF、EF FF,

    UTF-8也使用了辨别码,但所有UTF-8字符都是3个字节的,辩证码是EF BB BF

    而Ansi模式其实应该使用的是GB2312编码,没有辨别码,所以需要默认的编码,中文Windows就默认GB2312。编码每个中文用2个字节.


    说研究就研究,尽量一丝不苟。
    仔细研究了一下我们那个Properties的过程,发现原理是这个样子的。
    首先对比了一下文件的Hex的编码情况:
    使用记事本存储“我是人”三个字,发现如下:
    首先可以发现Unicode、Unicode-Bigendin前面都有辨别码,分别是FF EF、EF FF,以此区别编码序列,也同时声明了这是一个Unicode的文件。每一个汉字使用两个字节。也就是说用的是UCS-4。
    而UTF-8也使用了辨别码,但所有UTF-8字符都是3个字节的,辩证码是EF BB BF,和Unicode的辩证码其实是一个字符。每个汉字用3个字节表示。学习相关知识可知Unicode和UTF-8是可以直接映射的,两者影射其实是兼容的。
    而Ansi模式其实应该使用的是GB2312编码,没有辨别码,所以需要默认的编码,中文Windows就默认GB2312。编码每个中文用2个字节,编码以后的内容和Unicode不同。
    继续分析我们开发所用的环境的过程:
    首先,分析Eclipse写出来的文件,此时我设置的工作空间编码为UTF-8,发现文件没有辨别码,也就是说这个文件被其他编辑器打开不可能知道这是什么编码。而文件里面其实根本就没有使用UTF-8编码,因为所有的中文都是2个字节的编码。我怀疑是UCS-4,所以分析一下,在刚才用记事本写的文件里面写入“美食同盟网”察看Hex状态的编码。
    在Eclipse的文件里面“美”编码0xC3C0。Unicode里面是0x8E7F。Ansi里面是0xC3C0。Unicode-big里面肯定是0X7F8E,和Unicode那个反过来没的说。
    所以,呵呵,我想一切旋疑都解开了。我们敬爱的Eclipse不管如何设置,其实他还是使用了你的操作系统的默认编码,而我们这里就是GB2312了,即使我们设置了UTF-8。其实问题是这样的,本来Eclipse默认latin-1编码,如果用那个方式,写了中文的双字节字符以后再打开就会造成乱码,而设置了UTF-8以后能解决以多字节(2个、3个)的方式打开文件的问题,不过其实还是用GB2312打开,这个可能是Eclipse对Properties文件未知格式的原因,因为用Eclipse打开存储UTF-8的XML文件是没有问题的,这个大家都有经验。由于Eclipse的多字节文件没有存储识别码,所以XML文件还有JSP文件的Charset一定要声明UTF-8,否则还是有可能出问题。
    然后到这里已经真相大白了,这样再解释一下前几天出现的那些问题:
    1、由于单字节多字节的问题和Eclipse不存储识别码的问题,我们最好把工作环境强行换到一个多字节环境,比如UTF-8。防止互相出现乱码,大家最好都用中文的Windows,否则估计还有可能出现问题。
    2、解释上次native2ascii -encoding gb2312 ApplicationResources.properties ApplicationResources_zh.properties这个命令,网上那位老兄还说为什么Encoding改成UTF-8不行呢,因为你的Windows默认的就是gb2312,设UTF-8当然乱码。
    3、解释native2ascii。其实这个过程和UTF-8没有任何关系,而且编码出来的文件的确是Unicode的转义字符。其实编出来的文件根本本就是最普通的单字节Ascii文件,只不过它把双字节的Hex方式直接用明文存储为单字节Ascii文件了,转换明文使用Big endian方式,也就是高位在后。为什么这样呢,因为Java多字节只支持Unicode,或者说是遗留,反正Java没有直接支持UTF-8,内部传输都使用Unicode。这不是问题,因为映射相同输出的时候Java很容易把Unicode转为UTF-8。而我们转那个文件只不过为了让Java可以轻松的把你的文件从转义字符转化为Unicode,过成就比如把“/uC3C0”这样的自符串转化为“美”的编码0xC3C0了。这么说有点乱,过程其实如下:
    “美”在Eclipse存,然后在文件中如果用Hex察看是[C3 C0]。
    然后用Native2ascii转,你看不到“美”只能看到u7f8e,而这时候如果用Hex察看是[5C 75 37 66 38 65]这么大一长串Ascii了。
    这次明白了吧。
    4、关于UTF-8和Unicode还有UCS的东西大家看看资料吧,一搜一大把。
    UTF-8 and Unicode FAQ(中文,这个很官方,很有价值)
    http://www.linuxfans.org/nuke/modules.php?name=News&file=article&op=view&sid=1749
    谈谈Unicode编码 简要解释UCS/UTF/BMP/BOM(说的很通俗,而且涉及到的细节多,帮助大)
    http://news.onlinedown.net/info/13164-1.htm

    原创粉丝点击