中文问题的根源
我们都知道,西欧语系,都用一个字节来存储字符,而东方以汉语为主的语系都是用两个字节来存储字符..东西方文化的差异便导至了大多数程序员无一例外的会碰到中文处理的问题.java程序,也是如此.
关于字符处理的基础知识
字符内码
每个国家(或区域)都规定了计算机信息交换用的字符编码集,如美国的扩展ASCII码、中国的GB2312-80、日本的 JIS 等,作为该国家(区域)信息处理的基础,有着统一编码的重要作用。由于各本地字符集代码范围重叠,相互间信息交换困难,软件本地化版本独立维护成本较高。因此有必要将本地化工作中的共性抽取出来,做一致性处理,将特殊的本地化处理内容降低到最少,这就是所谓的国际化(I18N)。各种语言信息被规范为本地信息,而底层字符集采用包含了所有字符的Unicode。
字符内码(character code)指的是用来代表字符的内码。我们在输入和存储文档时都要使用内码,内码分为单字节内码和双字节内码。单字节内码的英文全称是Single-Byte Character Sets (SBCS),可以支持256个字符编码;双字节内码的英文全称是Double-Byte Character Sets(DBCS),可以支持65000个字符编码,主要用来对大字符集的东方文字进行编码。
CodePage(字符内码表)
CodePage指的是一个经过挑选的以特定顺序排列的字符内码列表,对于早期的单字节内码的语种,CodePage中的内码顺序使得系统可以按照此列表来根据键盘的输入值给出一个对应的内码。对于双字节内码,给出的是MultiByte到Unicode的对应表,这样就可以把以Unicode形式存放的字符转化为相应的字符内码。引入对CodePage的支持主要是为了访问多语种文件名,目前在NTFS和FAT32/VFAT下的文件系统上都使用Unicode,这需要系统在读取这些文件名时动态地将其转换为相应的语言编码。
microsoft对这个问题处理得比较好,在其官方网站也列出了一些国际通用的内码转换表..可以从以下地址下载使用.
ISO Character Set 8859-1 (Latin 1)
http://www.microsoft.com/globaldev/reference/iso/28591.htm
iso-8859-2.tbl
ISO Character Set 8859-2 (Latin 2)
http://www.microsoft.com/globaldev/reference/iso/28592.htm
iso-8859-3.tbl
ISO Character Set 8859-3 (Latin 3)
http://www.microsoft.com/globaldev/reference/iso/28593.htm
iso-8859-4.tbl
ISO Character Set 8859-4 (Baltic)
http://www.microsoft.com/globaldev/reference/iso/28594.htm
iso-8859-5.tbl
ISO Character Set 8859-5 (Cyrillic)
http://www.microsoft.com/globaldev/reference/iso/28595.htm
iso-8859-6.tbl
ISO Character Set 8859-6 (Arabic)
http://www.microsoft.com/globaldev/reference/iso/28596.htm
iso-8859-7.tbl
ISO Character Set 8859-7 (Greek)
http://www.microsoft.com/globaldev/reference/iso/28597.htm
iso-8859-8.tbl
ISO Character Set 8859-8 (Hebrew)
http://www.microsoft.com/globaldev/reference/iso/28598.htm
iso-8859-9.tbl
ISO Character Set 8859-9 (Turkish)
http://www.microsoft.com/globaldev/reference/iso/28599.htm
iso-8859-10.tbl
ISO Character Set 8859-10
http://www.unicode.org/Public/MAPPINGS/ISO8859/8859-10.TXT
iso-8859-11.tbl
ISO Character Set 8859-11 (Thai)
http://czyborra.com/charsets/iso8859.html
http://aspell.net/charsets/iso8859.html
http://www.unicode.org/Public/MAPPINGS/ISO8859/8859-11.TXT
iso-8859-13.tbl
ISO Character Set 8859-13 (Lithuanian)
http://www.unicode.org/Public/MAPPINGS/ISO8859/8859-13.TXT
iso-8859-14.tbl
ISO Character Set 8859-14 (Celtic)
http://www.unicode.org/Public/MAPPINGS/ISO8859/8859-10.TXT
iso-8859-15.tbl
ISO Character Set 8859-15 (Latin 9)
http://www.microsoft.com/globaldev/reference/iso/28605.htm
iso-8859-16.tbl
ISO Character Set 8859-16 (Romanian)
http://www.unicode.org/Public/MAPPINGS/ISO8859/8859-16.TXT
windows-1250.tbl
Microsoft Windows Codepage : 1250 (Central Europe)
http://www.microsoft.com/globaldev/reference/sbcs/1250.htm
windows-1251.tbl
Microsoft Windows Codepage : 1251 (Cyrillic)
http://www.microsoft.com/globaldev/reference/sbcs/1251.htm
windows-1252.tbl
Microsoft Windows Codepage : 1252 (Latin I)
http://www.microsoft.com/globaldev/reference/sbcs/1252.htm
windows-1253.tbl
Microsoft Windows Code Page : 1253 (Greek)
http://www.microsoft.com/globaldev/reference/sbcs/1253.htm
windows-1254.tbl
Microsoft Windows Codepage : 1254 (Turkish)
http://www.microsoft.com/globaldev/reference/sbcs/1254.htm
windows-1255.tbl
Microsoft Windows Codepage : 1255 (Hebrew)
http://www.microsoft.com/globaldev/reference/sbcs/1255.htm
windows-1256.tbl
Microsoft Windows Codepage : 1256 (Arabic)
http://www.microsoft.com/globaldev/reference/sbcs/1256.htm
windows-1257.tbl
Microsoft Windows Codepage : 1257 (Baltic)
http://www.microsoft.com/globaldev/reference/sbcs/1257.htm
windows-1258.tbl
Microsoft Windows Codepage : 1258 (Viet Nam)
http://www.microsoft.com/globaldev/reference/sbcs/1258.htm
windows-874.tbl
Microsoft Windows Codepage : 874 (Thai)
http://www.microsoft.com/globaldev/reference/sbcs/874.htm
shift_jis.tbl
Microsoft Windows Codepage : 932 (Japanese Shift-JIS)
http://www.microsoft.com/globaldev/reference/dbcs/932.htm
(Multibyte)
gb2312.tbl
Microsoft Windows Codepage : 936 (Simplified Chinese GBK)
gb2312 936 Chinese Simplified (GB2312)
gb_2312-80 936 Chinese Simplified (GB2312)
http://www.microsoft.com/globaldev/reference/dbcs/936.htm
(Multibyte)
Note: this is a MS-specific superset of the real GB2312
euc-kr.tbl
Microsoft Windows Codepage : 949 (Korean EUC-KR)
http://www.microsoft.com/globaldev/reference/dbcs/932.htm
(Multibyte)
Note: this is a MS-specific superset of the real EUC-KR
big5.tbl
Microsoft Windows Codepage : 950 (Traditional Chinese Big5)
http://www.microsoft.com/globaldev/reference/dbcs/950.htm
(Multibyte)
Note: this is a MS-specific superset of the real Big5
koi8-r.tbl
Cyrillic (Russian)
http://www.unicode.org/Public/MAPPINGS/VENDORS/MISC/KOI8-R.TXT
jsp使用的内码表
相信了解JSP代码的读者对ISO8859-1一定不陌生,ISO8859-1是我们平时使用比较多的一个CodePage,它属于西欧语系。
中文使用的内码表
GB2312-80 是在国内计算机汉字信息技术发展初始阶段制订的,其中包含了大部分常用的一、二级汉字和9区的符号。该字符集是几乎所有的中文系统和国际化的软件都支持的中文字符集,这也是最基本的中文字符集。
GBK 是 GB2312-80 的扩展,是向上兼容的。它包含了20902个汉字,其编码范围是 0x8140~0xFEFE,剔除高位 0x80 的字位,其所有字符都可以一对一映射到 Unicode 2.0,也就是说 Java 实际上提供了对 GBK 字符集的支持。
GB18030-2000(GBK2K) 在 GBK 的基础上进一步扩展了汉字,增加了藏、蒙等少数民族的文字。GBK2K 从根本上解决了字位不够、字形不足的问题。
jsp程序的执行过程
一个以.jsp为扩展名的请求到达tomcat之类的服务器里,,首先将会被服务器(一般称作servlets引擎)解析编辑成一个类,然后交由java虚拟机所装载的类执出,输出结果给客户端浏览器.这个过程便要通过两个步骤,其一为servlets引擎,其二便是java虚拟机.
大部分问题就出在sevlets引警上.
一个例子:
任一建一文档:命名为index.jsp,内容为:
<p>天涯风云</p>
这是个最普通不过的html文档,但加上了.jsp为扩展名.我们看看服务器的返回结果:
此主题相关图片如下:
一串乱码,,没人知道是什么..我们到服务器工作的临时文档去看看,看能找到些什么.
我在"D:/mysever/AppServer/work/Catalina/localhost/jsp-examples/org/apache/jsp"(注:我用的是集成安装工具,可能跟你的tomcat有差别,可以在你相应的目录里找找看)目录下找到以下两个文件:
index_jsp.class index_jsp.java
很熟悉吧,一个是.class文件,一个是.java,,不难想象,后一个文件是源文件了..看看源码吧.
'index_jsp.java
package org.apache.jsp;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
public final class index_jsp extends org.apache.jasper.runtime.HttpJspBase
implements org.apache.jasper.runtime.JspSourceDependent {
private static java.util.Vector _jspx_dependants;
public java.util.List getDependants() {
return _jspx_dependants;
}
public void _jspService(HttpServletRequest request, HttpServletResponse response)
throws java.io.IOException, ServletException {
JspFactory _jspxFactory = null;
PageContext pageContext = null;
HttpSession session = null;
ServletContext application = null;
ServletConfig config = null;
JspWriter out = null;
Object page = this;
JspWriter _jspx_out = null;
PageContext _jspx_page_context = null;
try {
_jspxFactory = JspFactory.getDefaultFactory();
response.setContentType("text/html");
pageContext = _jspxFactory.getPageContext(this, request, response,
null, true, 8192, true);
_jspx_page_context = pageContext;
application = pageContext.getServletContext();
config = pageContext.getServletConfig();
session = pageContext.getSession();
out = pageContext.getOut();
_jspx_out = out;
out.write("/r/n");
out.write("<p>ÌìÑÄ•çÔÆ</p>");
} catch (Throwable t) {
if (!(t instanceof SkipPageException)){
out = _jspx_out;
if (out != null && out.getBufferSize() != 0)
out.clearBuffer();
if (_jspx_page_context != null) _jspx_page_context.handlePageException(t);
}
} finally {
if (_jspxFactory != null) _jspxFactory.releasePageContext(_jspx_page_context);
}
}
}
从源码中可以看出,中文"天涯风云"几个字已经被解析成了"ÌìÑÄ•çÔÆ"一串乱码.
我们再写一个测试程序,来看看servlets引擎与java编辑器默认使用的内码转换方式是否一样.
'test.java
import java.lang.String;
public class test{
public static void main(String args[]){
String yyh="天涯风云";
System.out.println(yyh);
}
}
此主题相关图片如下:
看来编译器正确的处理该中文字串..默认情况下,java编译器采用机器内的码表来编译程序..来看看我的机子上的码是多少.
'test1.java
import java.lang.System;
import java.lang.String;
public class test1{
public static void main(String args[]){
System.out.println(System.getProperty("file.encoding"));
}
}
此主题相关图片如下:
看来我的系统encoding值是GB18030
此主题相关图片如下:
输出也正常..说明编译器是采用的encoding GB18030
那么,用GB2312呢?
此主题相关图片如下:
也是正常的。。我们再用iso8859_1试下,,据说是servlets引擎编译时采用的码转换方式。。
此主题相关图片如下:
按此查看图片详细信息
再次出现了乱码,不过看起来和servlets解析结果不一样哟!~~
如何处理显示乱码的问题呢?
page指令
jsp一共有两种指令,其一为page,其二include.而page指令作用于当前整个页面,配置当前页面所需要的一些必要信息.
page指令有六个属性,分别为language(指明编程用的语言),import(载入包语句),session(缺省为true),errorPage(出错指向的页面),contentType(指定了MIME的类型和JSP文件的字符编码方式),而contentTye便是我们处理中文时必须注意的一个地方,缺省的字符编码方式:ISO8859-1.
回到示例1中的index.jsp,怎么才能让它正常显示呢?
简单的方法便是,在文件开头加上一句:
<%@ page language="java" contentType="text/html;charset=gb2312" %>
这回我们用下面这段试下:
'index.jsp
<%@ page language="java" contentType="text/html;charset=gb2312" %>
<p>天涯风云</p>
此主题相关图片如下:
这回显示正常了,,看看servlets引擎解析后的index_jsp.java文件源码:
package org.apache.jsp;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
public final class index_jsp extends org.apache.jasper.runtime.HttpJspBase
implements org.apache.jasper.runtime.JspSourceDependent {
private static java.util.Vector _jspx_dependants;
public java.util.List getDependants() {
return _jspx_dependants;
}
public void _jspService(HttpServletRequest request, HttpServletResponse response)
throws java.io.IOException, ServletException {
JspFactory _jspxFactory = null;
PageContext pageContext = null;
HttpSession session = null;
ServletContext application = null;
ServletConfig config = null;
JspWriter out = null;
Object page = this;
JspWriter _jspx_out = null;
PageContext _jspx_page_context = null;
try {
_jspxFactory = JspFactory.getDefaultFactory();
response.setContentType("text/html;charset=gb2312");
pageContext = _jspxFactory.getPageContext(this, request, response,
null, true, 8192, true);
_jspx_page_context = pageContext;
application = pageContext.getServletContext();
config = pageContext.getServletConfig();
session = pageContext.getSession();
out = pageContext.getOut();
_jspx_out = out;
out.write("/r/n");
out.write("<p>天涯风云</p>");
} catch (Throwable t) {
if (!(t instanceof SkipPageException)){
out = _jspx_out;
if (out != null && out.getBufferSize() != 0)
out.clearBuffer();
if (_jspx_page_context != null) _jspx_page_context.handlePageException(t);
}
} finally {
if (_jspxFactory != null) _jspxFactory.releasePageContext(_jspx_page_context);
}
}
}
也解析正常了。。这同时又说明,你在写servlets时,注意加上这句:
response.setContentType("text/html;charset=gb2312");
才能保证你的中文得到正确处理。。当然,你也可以用其它的字符集试试。
表单的中文处理问题
IE默认的表单变量的字符传递方式是utf-8.
此主题相关图片如下:
于是,变量在传递过程中,再次出现了编码的问题.
我们还是来举个例子,,看看表单提交变量中文字符又会怎么样被处理.
.index1.jsp
<%@ page contentType="text/html;charset=gb2312"%>
<%
if(request.getParameter("name")!=null){
String keyword1=request.getParameter("name");
out.print(keyword1);
keyword1="";
}
%>
<form action="index1.jsp" method="POST" name=myform>
<input type=text name="name">
<input type="submit" name=ok value="ok"></form>
此主题相关图片如下:
处理后的结果:
此主题相关图片如下:
乱码问题再次出现,,尽管我们在页面前注明了charset=gb2312
当我把把页前改成charset=utf-8时,,还是出现了乱码
'index1.jsp
<%@ page contentType="text/html;charset=utf-8"%>
<%
if(request.getParameter("name")!=null){
String keyword1=request.getParameter("name");
out.print(keyword1);
keyword1="";
}
%>
<form action="index1.jsp" method="POST" name=myform>
<input type=text name="name">
<input type="submit" name=ok value="ok"></form>
处理结果
最后,我再次测试,用:charset=8859_1
,index1.jsp
<%@ page contentType="text/html;charset=8859_1"%>
<%
if(request.getParameter("name")!=null){
String keyword1=request.getParameter("name");
out.print(keyword1);
keyword1="";
}
%>
<form action="index1.jsp" method="POST" name=myform>
<input type=text name="name">
<input type="submit" name=ok value="ok"></form>
显示正常的中文字符:
此主题相关图片如下:
如果我们页面都使用charset=8859_1,也许不实为一个解决方案..呵呵,,
测试中,居然使用一个错误的表达,也能正常显示,,奇怪~~~
<%@ page contentType="text/html;charset:charset=gb2312"%>
这样子也能使汉字正常显示,,原因还不清楚,,.
还有一种方法就是使用
request.setCharacterEncoding("gb2312");
在接收变量前转换成gb2312..
'index1.jsp
<%@ page contentType="text/html;charset=gb2312"%>
<%
request.setCharacterEncoding("gb2312");
if(request.getParameter("name")!=null){
String keyword1=request.getParameter("name");
out.print(keyword1);
keyword1="";
}
%>
<form action="index1.jsp" method="POST" name=myform>
<input type=text name="name">
<input type="submit" name=ok value="ok"></form>
此主题相关图片如下:
也显示正常的值..
推荐两个常用的函数:
public String getStr(String s){
String str=s;
try{
byte b[]=str.getBytes("ISO-8859-1");
str=new String(b);
return str;
}
catch(Exception e){return null;}
}
public String gb2iso(String qs){
try{
if (qs == null) return "NULL";
else return new String(qs.getBytes("gb2312"),"iso-8859-1");
}
catch(Exception e){
System.out.print("gb2iso error:"+e.getMessage());
}
return "NULL";
}
可以把这两个函数封装到类中,,用的时侯方便调用..
最后结论:
客户端:输入法Unicode--输入框unicode--从Unicode按charset转换到对应编码()--表单用utf-8发送编码
服务器端:servlets引擎解开表单编码--按codepage指定编码读取--转换到对应的Unicode--用servlets编译,生成class文件,交由java虚机处理,发送回客户端浏览器
客户端:IE按charset读取显示。
1.纯jsp页面,charset设为gb2312,servlets引擎用指定的gb2312译成class后,由java虚机运行,送回客户端浏览器,客户端用charset设为gb2312读取,页面内汉字字符正常显示.
2.有表单的jsp页面,charset设为gb2312,显示过程同上.
执行过程:字符串提交后,用utf-8编译传送,servlets引擎用gb2312编译,java虚机作出反应,执行显示页面的类似过程,客户端还是用gb2312读取,页面汉字字符显示不正常.
3.用表单jsp页面,charset设为iso8859-1,servlets引擎用iso8859-1编译,客户端用iso8859-1读取,显示也正常;执行过程:字符串提交后,用uft-8编译,servlet引擎作出显示过程类似步骤,客户端用iso8859-1读取,页面汉字也显示正常.
4.如果全部设为utf-8后,因为页内文件保存时默认的是gb2312,servlets用utf-8处理后,汉字还是出现乱码.
此主题相关图片如下:
(注:我用的编辑工具是Emeditor)
如果把文件另存为时选择utf-8,则页内汉字也显示正常.
此主题相关图片如下:
5.是否把页面全部设为utf-8就解决了问题呢?
如上图所示的作法,将会出现这样的结果:
此主题相关图片如下:
原来
,,
这里面还有一个输入法的问题
,
此主题相关图片如下:
我用的王码五笔86版因为是gb2312,汉字字符被通过utf-8处理后,乱码再次出现.如果从别处copy过来的,便系统内默认是gb18030,还是要经过utf-8这一关,如是也会有乱码.
那么,如何解决呢?看下面这段代码:
<%@ page contentType="text/html;charset=utf-8"%>
<%
request.setCharacterEncoding("utf-8");
if(request.getParameter("name")!=null){
String keyword1=request.getParameter("name");
out.print("您的姓名是:"+keyword1);
keyword1="";
}
%>
<form action="index.jsp" method="POST" name=myform>
请输入姓名:
<input type=text name="name">
<input type="submit" name=ok value="ok"></form>
将字符集强制设为utf-8后,,就不再显示乱了.当然,在保存文件时,也不要忘记设为utf-8哟~~.
此主题相关图片如下:
看来,我们必须注意几个关键部分是:
1.页面charset的设置
2.变量接收后的转换
3.文件保存时所使用的字符集.
4.系统内默认的字符集
5.输入法所使用的字符集.
补充:jsp与mysql与避免乱码的解决方案:
1.设置web容器的编码格式。为你的servlet的doGet或doPost方法开始处加入如下代码:
request.setCharacterEncoding("utf-8");
response.setCharacterEncoding("utf-8");
2.为每个jsp页面指定其编码格式。<%@ page pageEncoding="utf-8"%>
3.在连接数据库用的URL后加入:useUnicode=true;characterEncoding=utf-8 如:
url="jdbc:mysql:///db1?useUnicode=true;characterEncoding=utf-8",
4.为指定数据库默认编码格式。在C:/WINDOWS目录下找到my.ini文件,并在[mysqld]中加入default-character-set=gbk,重新启动mysql服务。
至此,乱码问题全部解决。起初总搞上去不清,为什么要将mysql的默认编码格式设置为gbk,后来由相关的资料得知utf-8默认输入编码方式为gbk,默认输出编码方式为utf-16be。