强烈推荐一篇文章:理解字符编码

在写程序的过程中,字符集的问题遇到过N多。查查改改,解决问题,一直没有总结。这段时间做J2EE的项目,遇到了更多的字符集问题,在解决的过程中收获不少。写篇文章浅谈下字符集问题。

个人将字符集粗分为两大类:ASCII和UNICODE。简单介绍下它们:

众所周知,计算机是美国人发明的。因此,为了方便使用计算机,那么计算机起码要能表达他们(美国人)的语言。这也就是ASCII(美国信息交换标准代码)的由来了。计算机的最小信息单位是位(bit),但更常用的基本信息单位是字节(Byte),它们之间的转换是1Byte = 8bits(为什么是8而不是2、4、16等,我就不得而知了)。ASCII又分基础的(7位,最高位用作奇偶校验)和扩展的(8位),最多能表示256个字符。这对于表示全世界的语言字符是不可能的,因此有了UNICODE。

UNICODE简单的来说就是,用两个字节(16位)来表示一个字符。但是基于不同的考虑(如存储空间)又有UTF-8、UTF-16、UTF-32等不同的格式,这里就不详细介绍了。

正是ASCII和UNICODE给我们带来了字符集问题。一个比较好的程序,应该要能支持UNICODE。对于我们(中国人),更是如此,因为我们使用的是汉字而不是英文。接下来,我要以一个不完全的Java Web Server例子来说说个人的理解。

OS(Windows、Unix、Linux等)、Java、MySQL在全世界流行,因此很容易预见到它们都是支持ASCII和UNICODE的。先说说OS,个人用的是Windows XP(中文版)(Windows对于本地化的支持有个“代码页”的概念,在此不详细介绍),很容易知道OS的字符集是UNICODE的GBK。对于运行在该OS上的Java平台,也是如此。而MySQL对字符集的支持主要表现在它的存储。

在例子中,MySQL采用latin1(称之为ASCII或者ISO-8859-1都行),而JSP采用GB2312。

问题一:JSP page指令中的pageEncoding属性指定的字符集是指该JSP页面被打开时,OS用哪种字符集格式读取。这就相当于你在记事本里写篇文字(即有英文又有中文),保存的格式。如果为ASCII,则中文会丢失原有的信息,为UNICODE则能正常显示。因此,其实只要你在该JSP里面的注释仅有英文,是可以设置该属性为ISO-8859-1的。

问题二:JSP page指令中的contentType="text/html;charset=gb2312"和HTML中的<meta http-equiv="Content-Type" content="text/html; charset=gb2312" />意思一样,都是指该页面由浏览器解析时采用的字符集。

问题三:Java中所有字符都是两个字节,无论是ASCII还是UNICODE,但是建议你不要简单地认为它就是UNICODE。而网络上传输的字符是ISO-8859-1(1Byte)的(因为网络传输是基于字节流的)。所以前一个JSP页面的中文参数采用POST方法传到后一个页面接收后,它是乱码。详细:一个汉字两个字节都有数据**(GB2312),网络传输成* *(ISO-8859-1),后一个页面接收后成#* #*(GB2312)两个汉字四个字节。(*表示数据,#表示未知,空格表示字符间隔)当然乱码了。重新组合下new String(data.getBytes("ISO-8859-1"))就好了(默认采用页面charset编码组合即GB2312)。采用GET方法则要在前一个页面先将中文编码后再在URL中传递参数:String msg = java.net.URLEncoder.encode("中文", "UTF-8");在后一个页面取得msg后,对应的使用new String(msg.getBytes("ISO-8859-1"), "UTF-8")或者java.net.UREDecoder.decode(msg, "UTF-8");。

问题四:数据库的字符集与Java Web Server和request对象的字符集最好一致。不过,不一致也没有关系,怎么写就怎么读。例如我的设计是页面1负责收集参数,页面2负责处理参数,JavaBean负责数据库操作。那么中文参数**到页面二的时候已经乱码成#* #*,JavaBean中数据库操作也是#* #*了。对于latin1的字符集,我想Java和MySQL交互的时候进行了这样的变化:#* #*变成了* *(注意这里的* *是两个ASCII字符,而不是最初的那个UNICODE字符了)。按理,在MySQL中select该记录,应该显示乱码,但是由于OS字符集是GBK的,因此latin1字符集数据库中的* *又被组合成了**正常显示了(个人理解)。

问题五:JavaScript、AJAX与JSP交互处理中文。

JS:value初始值为中文,xmlHttp为AJAX对象。

value = encodeURIComponent(value);//调用JS函数对中文进行UTF-8编码
value = "regajax.jsp?username=" + value;//URL
xmlHttp.onreadystatechange = stateChanged;//xmlHttp对象状态改变时调用函数stateChanged
xmlHttp.open("GET", value, true);
xmlHttp.send(null);

JSP:

String username = request.getParameter("username");//网络传输使用ISO-8859-1字符集,当参数到达页面时采用页面编码字符集,即GB2312。GB2312与UTF-8对简体中文的编码一致,故此时的username不乱码。
username = new String(username.getBytes("GB2312"), "ISO-8859-1");//MySQL采用ISO-8859-1字符集。插入记录时,我们对中文的处理是从**到#* #*,再到* *;查询的时候也应该如此处的#* #*到* *。

以上JSP代码在NetBeans中运行没有问题,但可能会产生问题七中的问题,这里提供一种Tomcat下的解决方案:

String username = request.getParameter("username");
username = new String(username.getBytes("ISO-8859-1"), "UTF-8");
username = new String(username.getBytes("GB2312"), "ISO-8859-1");//注册页面里POST方法传递给数据库的参数是由GB2312转成ISO-8859-1的,所以这里要一致。

问题六:MySQL的备份与恢复。稍后会用一整篇文章说明。

问题七(对问题三的补充):近日又发现,GET时在NetBeans中可以直接使用String username = request.getParameter("username");显示对中文进行UTF-8编码后在URL中传递的参数,而在Tomcat下还要使用new String(username.getBytes("ISO-8859-1"));或者new String(username.getBytes("ISO-8859-1"), "UTF-8");。原因暂不清楚。