??xml version="1.0" encoding="utf-8" standalone="yes"?>
]]>
深入剖析JSP和Servlet对中文的处理 |
作者:未知 来源Q{?a >http://www.sucai.com/article/show.asp?id=8142 |
q是一个世界范围内都存在的问题Q所以,Java提供了世界性的解决Ҏ。本文描q的Ҏ是用于处理中文的Q但是,推而广之,对于处理世界上其它国家和地区的语a同样适用?/p>
汉字是双字节的。所谓双字节是指一个双字要占用两个BYTE的位|(?6位)Q分别称为高位和低位。中国规定的汉字~码为GB2312Q这是强制性的Q目前几乎所有的能处理中文的应用E序都支持GB2312。GB2312包括了一二汉字?区符P高位?xa1?xfeQ低位也是从0xa1?xfeQ其中,汉字的编码范围ؓ0xb0a1?xf7fe?/p>
另外有一U编码,叫做GBKQ但q是一份规范,不是强制的。GBK提供?0902个汉字,它兼容GB2312Q编码范围ؓ0x8140?xfefe。GBK中的所有字W都可以一一映射到Unicode 2.0?/p>
在不久的来Q中国会颁布另一U标准:GB18030-2000QGBK2KQ。它收录了藏、蒙{少数民族的字型Q从Ҏ上解决了字位不的问题。注意:它不再是定长的。其二字节部份与GBK兼容Q四字节部分是扩充的字符、字形。它的首字节和第三字节从0x81?xfeQ二字节和第四字节从0x30?x39?/p>
本文不打介lUnicodeQ有兴趣的可以浏览“http://www.unicode.org/”查看更多的信息。Unicode有一个特性:它包括了世界上所有的字符字Ş。所以,各个地区的语a都可以徏立与Unicode的映关p,而Java正是利用了这一点以辑ֈ异种语言之间的{换?/p>
在JDK中,与中文相关的~码有:
? JDK中与中文相关的编码列?br />
~码名称 | 说明 |
ASCII | 7位,与ascii7相同 |
ISO8859-1 | 8-位,?8859_1,ISO-8859-1,ISO_8859-1,latin1...{相?/td> |
GB2312-80 | 16位,与gb2312,gb2312-1980,EUC_CN,euccn,1381,Cp1381, 1383, Cp1383, ISO2022CN,ISO2022CN_GB...{相?/td> |
GBK | 与MS936相同Q注意:区分大小?/td> |
UTF8 | 与UTF-8相同 |
GB18030 | 与cp1392?392相同Q目前支持的JDK很少 |
在实际编E时Q接触得比较多的是GB2312QGBKQ和ISO8859-1?/p>
Z么会有?”号
上文说过Q异U语a之间的{换是通过Unicode来完成的。假设有两种不同的语aA和BQ{换的步骤为:先把A转化为UnicodeQ再把Unicode转化为B?/p>
举例说明。有GB2312中有一个汉字“李”,其编码ؓ“C0EE”,Ʋ{化ؓISO8859-1~码。步骤ؓQ先把“李”字转化为UnicodeQ得到?74E”,再把?74E”{化ؓISO8859-1字符。当Ӟq个映射不会成功Q因为ISO8859-1中根本就没有与?74E”对应的字符?/p>
当映不成功Ӟ问题发生了Q当从某语言向Unicode转化Ӟ如果在某语言中没有该字符Q得到的是Unicode的代码“\uffffd”(“\u”表C是Unicode~码Q)。而从Unicode向某语言转化Ӟ如果某语a没有对应的字W,则得到的是?x3f”(?”)。这是?”的由来?/p>
例如Q把字符buf =?x80 0x40 0xb0 0xa1”进行new String(buf, "gb2312")操作Q得到的l果是“\ufffd\u554a”,再println出来Q得到的l果是?啊”,因ؓ?x80 0x40”是GBK中的字符Q在GB2312中没有?/p>
再如Q把字符串String="\u00d6\u00ec\u00e9\u0046\u00bb\u00f9"q行new String (buf.getBytes("GBK"))操作Q得到的l果是?fa8aca8a6463fa8b4”,其中Q“\u00d6”在“GBK”中没有对应的字W,得到?f”,“\u00ec”对应着“a8ac”,“\u00e9”对应着“a8a6”,?046”对应着?6”(因ؓq是ASCII字符Q,“\u00bb”没扑ֈQ得到?f”,最后,“\u00f9”对应着“a8b4”。把q个字符串println一下,得到的结果是?ìéF?ù”。看到没Q这里ƈ不全是问P因ؓGBK与Unicode映射的内容中除了汉字外还有字W,本例是最好的明证?/p>
所以,在汉字{码时Q如果发生错乱,得到的不一定都是问号噢Q不q,错了l究是错了,50步和100步ƈ没有质的差别?/p>
或者会问:如果源字W集中有Q而Unicode中没有,l果会如何?回答是不知道。因为我手头没有能做q个试的源字符集。但有一Ҏ肯定的,那就是源字符集不够规范。在Java中,如果发生q种情况Q是会抛出异常的?br />
什么是UTF
UTFQ是Unicode Text Format的羃写,意ؓUnicode文本格式。对于UTFQ是q样定义的:
Q?Q如果Unicode?6位字W的?位是0Q则用一个字节表C,q个字节的首位是?”,剩下?位与原字W中的后7位相同,如“\u0034”(0000 0000 0011 0100Q,用?4?(0011 0100)表示Q(与源Unicode字符是相同的Q;
Q?Q如果Unicode?6位字W的?位是0Q则?个字节表C,首字节是?10”开_后面?位与源字W中除去?个零后的最?位相同;W二个字节以?0”开_后面?位与源字W中的低6位相同。如“\u025d”(0000 0010 0101 1101Q,转化后ؓ“c99d”(1100 1001 1001 1101Q;
Q?Q如果不W合上述两个规则Q则用三个字节表C。第一个字节以?110”开_后四位ؓ源字W的高四位;W二个字节以?0”开_后六位ؓ源字W中间的六位Q第三个字节以?0”开_后六位ؓ源字W的低六位;如“\u9da7”(1001 1101 1010 0111Q,转化为“e9b6a7”(1110 1001 1011 0110 1010 0111Q;
可以q么描述JAVAE序中Unicode与UTF的关p,虽然不绝对:字符串在内存中运行时Q表CؓUnicode代码Q而当要保存到文g或其它介质中LQ用的是UTF。这个{化过E是由writeUTF和readUTF来完成的?/p>
好了Q基性的差不多了Q下面进入正题?/p>
先把q个问题x是一个黑匣子。先看黑匣子的一U表C:
input(charsetA)->process(Unicode)->output(charsetB)
单,q就是一个IPO模型Q即输入、处理和输出。同L内容要经q“从charsetA到unicode再到charsetB”的转化?/p>
再看二表示Q?/p>
SourceFile(jsp,java)->class->output
在这个图中,可以看出Q输入的是jsp和java源文Ӟ在处理过E中Q以Class文g体,然后输出。再l化CU表C:
jsp->temp file->class->browser,os console,db
app,servlet->class->browser,os console,db
q个囑ְ更明白了。Jsp文g先生成中间的Java文gQ再生成Class。而Servlet和普通App则直接编译生成Class。然后,从Class再输出到览器、控制台或数据库{?/p>
JSPQ从源文件到Class的过E?/p>
Jsp的源文g是以?jsp”结文本文g。在本节中,阐qJSP文g的解释和~译q程Qƈ跟踪其中的中文变化?/p>
1、JSP/Servlet引擎提供的JSP转换工具QjspcQ搜索JSP文g中用<%@ page contentType ="text/html; charset=<Jsp-charset>"%>中指定的charset。如果在JSP文g中未指定<Jsp-charset>Q则取JVM中的默认讄file.encodingQ一般情况下Q这个值是ISO8859-1Q?/p>
2、jspc用相当于“javac –encoding <Jsp-charset>”的命o解释JSP文g中出现的所有字W,包括中文字符和ASCII字符Q然后把q些字符转换成Unicode字符Q再转化成UTF格式Q存为JAVA文g。ASCII码字W{化ؓUnicode字符时只是简单地在前面加?0”,如“A”,转化为“\u0041”(不需要理由,Unicode的码表就是这么编的)。然后,l过到UTF的{换,又变回?1”了Q这也就是可以用普通文本编辑器查看由JSP生成的JAVA文g的原因;
3、引擎用相当于“javac –encoding UNICODE”的命oQ把JAVA文g~译成CLASS文gQ?/p>
先看一下这些过E中中文字符的{换情c有如下源代码:
<%@ page contentType="text/html; charset=gb2312"%>
<html><body>
<%
String a="中文";
out.println(a);
%>
</body></html>
q段代码是在UltraEdit for Windows上编写的。保存后Q“中文”两个字?6q制~码为“D6 D0 CE C4”(GB2312~码Q。经查表Q“中文”两字的Unicode~码为“\u4E2D\u6587”,?UTF表示是“E4 B8 AD E6 96 87”。打开引擎生成的由JSP文g转变而成的JAVA文gQ发现其中的“中文”两个字实被“E4 B8 AD E6 96 87”替代了Q再查看由JAVA文g~译生成的CLASS文gQ发现结果与JAVA文g中的完全一栗?/p>
再看JSP中指定的CharSet为ISO-8859-1的情c?/p>
<%@ page contentType="text/html; charset=ISO-8859-1"%>
<html><body>
<%
String a="中文";
out.println(a);
%>
</body></html>
同样Q该文g是用UltraEdit~写的,“中文”这两个字也是存为GB2312~码“D6 D0 CE C4”。先模拟一下生成的JAVA文g和CLASS文g的过E:jspc用ISO-8859-1来解释“中文”,q把它映到Unicode。由于ISO-8859-1?位的Q且是拉丁语p,其映规则就是在每个字节前加?0”,所以,映射后的Unicode~码应ؓ“\u00D6\u00D0\u00CE\u00C4”,转化成UTF后应该是“C3 96 C3 90 C3 8E C3 84”。好Q打开文g看一下,JAVA文g和CLASS文g中,“中文”果焉表示为“C3 96 C3 90 C3 8E C3 84”?/p>
如果上述代码中不指定<Jsp-charset>Q即把第一行写成?lt;%@ page contentType="text/html" %>”,JSPC会用file.encoding的设|来解释JSP文g。在RedHat 6.2上,其处理结果与指定为ISO-8859-1是完全相同的?/p>
到现在ؓ止,已经解释了从JSP文g到CLASS文g的{变过E中中文字符的映过E。一句话Q从“JspCharSet到Unicode再到UTF”。下表ȝ了这个过E:
? “中文”从JSP到CLASS的{化过E?br />
Jsp-CharSet | JSP文g?/td> | JAVA文g?/td> | CLASS文g?/td> |
GB2312 | D6 D0 CE C4(GB2312) | 从\u4E2D\u6587(Unicode)到E4 B8 AD E6 96 87 (UTF) | E4 B8 AD E6 96 87 (UTF) |
ISO-8859-1 | D6 D0 CE C4 (GB2312) | 从\u00D6\u00D0\u00CE\u00C4 (Unicode)到C3 96 C3 90 C3 8E C3 84 (UTF) | C3 96 C3 90 C3 8E C3 84 (UTF) |
无(默认Qfile.encodingQ?/td> | 同ISO-8859-1 | 同ISO-8859-1 | 同ISO-8859-1 |
ServletQ从源文件到Class的过E?/p>
Servlet源文件是以?java”结文本文g。本节将讨论Servlet的编译过Eƈ跟踪其中的中文变化?/p>
用“javac”编译Servlet源文件。javac可以带?encoding <Compile-charset>”参敎ͼ意思是“用< Compile-charset >中指定的~码来解释Serlvet源文件”?/p>
源文件在~译Ӟ?lt;Compile-charset>来解释所有字W,包括中文字符和ASCII字符。然后把字符帔R转变成Unicode字符Q最后,把Unicode转变成UTF?/p>
在Servlet中,q有一个地方设|输出流的CharSet。通常在输出结果前Q调用HttpServletResponse的setContentTypeҎ来达C在JSP中设|?lt;Jsp-charset>一L效果Q称之ؓ<Servlet-charset>?/p>
注意Q文中一共提C三个变量Q?lt;Jsp-charset>?lt;Compile-charset>?lt;Servlet-charset>。其中,JSP文g只与<Jsp-charset>有关Q?lt;Compile-charset>?lt;Servlet-charset>只与Servlet有关?/p>
看下例:
import javax.servlet.*;
import javax.servlet.http.*;
class testServlet extends HttpServlet
{
public void doGet(HttpServletRequest req,HttpServletResponse resp)
throws ServletException,java.io.IOException
{
resp.setContentType("text/html; charset=GB2312");
java.io.PrintWriter out=resp.getWriter();
out.println("<html>");
out.println("#中文#");
out.println("</html>");
}
}
该文件也是用UltraEdit for Windows~写的,其中的“中文”两个字保存为“D6 D0 CE C4”(GB2312~码Q?/p>
开始编译。下表是<Compile-charset>不同ӞCLASS文g中“中文”两字的十六q制码。在~译q程中,<Servlet-charset>不vM作用?lt;Servlet-charset>只对CLASS文g的输Z生媄响,实际上是<Servlet-charset>?lt;Compile-charset>一P辑ֈ与JSP文g中的<Jsp-charset>相同的效果,因ؓ<Jsp-charset>对编译和CLASS文g的输出都会生媄响?/p>
? “中文”从Servlet源文件到Class的{变过E?br />
Compile-charset | Servlet源文件中 | Class文g?/td> | {效的Unicode?/td> |
GB2312 | D6 D0 CE C4 (GB2312) | E4 B8 AD E6 96 87 (UTF) | \u4E2D\u6587 (在Unicode中=“中文? |
ISO-8859-1 | D6 D0 CE C4 (GB2312) | C3 96 C3 90 C3 8E C3 84 (UTF) | \u00D6 \u00D0 \u00CE \u00C4 (在D6 D0 CE C4前面各加了一?0) |
无(默认Q?/td> | D6 D0 CE C4 (GB2312) | 同ISO-8859-1 | 同ISO-8859-1 |
序号 | 步骤说明 | l果 |
1 | ~写JSP源文Ӟ且存为GB2312格式 | D6 D0 CE C4 QD6D0=?CEC4=文) |
2 | jspc把JSP源文件{化ؓ临时JAVA文gQƈ把字W串按照GB2312映射到UnicodeQƈ用UTF格式写入JAVA文g?/td> | E4 B8 AD E6 96 87 |
3 | 把时JAVA文g~译成CLASS文g | E4 B8 AD E6 96 87 |
4 | q行Ӟ先从CLASS文g中用readUTFd字符Ԍ在内存中的是Unicode~码 | 4E 2D 65 87Q在Unicode?E2D=?6587=文) |
5 | ҎJsp-charset=GB2312把Unicode转化为字节流 | D6 D0 CE C4 |
6 | 把字节流输出到IE中,q设|IE的编码ؓGB2312Q作者按Q这个信息隐藏在HTTP头中Q?/td> | D6 D0 CE C4 |
7 | IE用“简体中文”查看结?/td> | “中文”(正确昄Q?/td> |
序号 | 步骤说明 | l果 |
1 | ~写JSP源文Ӟ且存为GB2312格式 | D6 D0 CE C4 QD6D0=?CEC4=文) |
2 | jspc把JSP源文件{化ؓ临时JAVA文gQƈ把字W串按照ISO8859-1映射到UnicodeQƈ用UTF格式写入JAVA文g?/td> | C3 96 C3 90 C3 8E C3 84 |
3 | 把时JAVA文g~译成CLASS文g | C3 96 C3 90 C3 8E C3 84 |
4 | q行Ӟ先从CLASS文g中用readUTFd字符Ԍ在内存中的是Unicode~码 | 00 D6 00 D0 00 CE 00 C4 Q啥都不是!Q!Q?/td> |
5 | ҎJsp-charset=ISO8859-1把Unicode转化为字节流 | D6 D0 CE C4 |
6 | 把字节流输出到IE中,q设|IE的编码ؓISO8859-1Q作者按Q这个信息隐藏在HTTP头中Q?/td> | D6 D0 CE C4 |
7 | IE用“西Ƨ字W”查看结?/td> | qQ其实是四个ASCII字符Q但׃大于128Q所以显C出来的怪模怪样 |
8 | 改变IE的页面编码ؓ“简体中文?/td> | “中文”(正确昄Q?/td> |
序号 | 步骤说明 | l果 |
1 | ~写JSP源文Ӟ且存为GB2312格式 | D6 D0 CE C4 QD6D0=?CEC4=文) |
2 | jspc把JSP源文件{化ؓ临时JAVA文gQƈ把字W串按照ISO8859-1映射到UnicodeQƈ用UTF格式写入JAVA文g?/td> | C3 96 C3 90 C3 8E C3 84 |
3 | 把时JAVA文g~译成CLASS文g | C3 96 C3 90 C3 8E C3 84 |
4 | q行Ӟ先从CLASS文g中用readUTFd字符Ԍ在内存中的是Unicode~码 | 00 D6 00 D0 00 CE 00 C4 |
5 | ҎJsp-charset=ISO8859-1把Unicode转化为字节流 | D6 D0 CE C4 |
6 | 把字节流输出到IE?/td> | D6 D0 CE C4 |
7 | IE用发求时的页面的~码查看l果 | 视情况而定。如果是体中文,则能正确昄Q否则,需执行?中的W??/td> |
序号 | 步骤说明 | l果 |
1 | ~写Servlet源文Ӟ且存为GB2312格式 | D6 D0 CE C4 QD6D0=?CEC4=文) |
2 | 用javac –encoding GB2312把JAVA源文件编译成CLASS文g | E4 B8 AD E6 96 87 QUTFQ?/td> |
3 | q行Ӟ先从CLASS文g中用readUTFd字符Ԍ在内存中的是Unicode~码 | 4E 2D 65 87 (Unicode) |
4 | ҎServlet-charset=GB2312把Unicode转化为字节流 | D6 D0 CE C4 (GB2312) |
5 | 把字节流输出到IE中ƈ讄IE的编码属性ؓServlet-charset=GB2312 | D6 D0 CE C4 (GB2312) |
6 | IE用“简体中文”查看结?/td> | “中文”(正确昄Q?/td> |
序号 | 步骤说明 | l果 |
1 | ~写Servlet源文Ӟ且存为GB2312格式 | D6 D0 CE C4 QD6D0=?CEC4=文) |
2 | 用javac –encoding ISO8859-1把JAVA源文件编译成CLASS文g | C3 96 C3 90 C3 8E C3 84 QUTFQ?/td> |
3 | q行Ӟ先从CLASS文g中用readUTFd字符Ԍ在内存中的是Unicode~码 | 00 D6 00 D0 00 CE 00 C4 |
4 | ҎServlet-charset=ISO8859-1把Unicode转化为字节流 | D6 D0 CE C4 |
5 | 把字节流输出到IE中ƈ讄IE的编码属性ؓServlet-charset=ISO8859-1 | D6 D0 CE C4 (GB2312) |
6 | IE用“西Ƨ字W”查看结?/td> | qQ原因同?Q?/td> |
7 | 改变IE的页面编码ؓ“简体中文?/td> | “中文”(正确昄Q?/td> |
序号 | 步骤说明 | l果 | ?/td> |
1 | 在IE中输入“中文?/td> | D6 D0 CE C4 | IE |
2 | IE把字W串转变成UTFQƈ送入传输中 | E4 B8 AD E6 96 87 | |
3 | Servlet接收到输入流Q用readUTFd | 4E 2D 65 87(unicode) | Servlet |
4 | ~程者在Servlet中必L字符串根据GB2312q原为字节流 | D6 D0 CE C4 | |
5 | ~程者根据数据库内码ISO8859-1生成新的字符?/td> | 00 D6 00 D0 00 CE 00 C4 | |
6 | 把新生成的字W串提交lJDBC | 00 D6 00 D0 00 CE 00 C4 | |
7 | JDBC到数据库内码ؓISO8859-1 | 00 D6 00 D0 00 CE 00 C4 | JDBC |
8 | JDBC把接收到的字W串按照ISO8859-1生成字节?/td> | D6 D0 CE C4 | |
9 | JDBC把字节流写入数据库中 | D6 D0 CE C4 | |
10 | 完成数据存储工作 | D6 D0 CE C4 数据?/td> | |
以下是从数据库中取出数的q程 | |||
11 | JDBC从数据库中取出字节流 | D6 D0 CE C4 | JDBC |
12 | JDBC按照数据库的字符集ISO8859-1生成字符Ԍq提交给Servlet | 00 D6 00 D0 00 CE 00 C4 (Unicode) | |
13 | Servlet获得字符?/td> | 00 D6 00 D0 00 CE 00 C4 (Unicode) | Servlet |
14 | ~程者必L据数据库的内码ISO8859-1q原成原始字节流 | D6 D0 CE C4 | |
15 | ~程者必L据客L字符集GB2312生成新的字符?/td> | 4E 2D 65 87 QUnicodeQ?/td> | |
Servlet准备把字W串输出到客L | |||
16 | ServletҎQServlet-charsetQ生成字节流 | D6D0 CE C4 | Servlet |
17 | Servlet把字节流输出到IE中,如果已指定<Servlet-charsetQ,q会讄IE的编码ؓQServlet-charsetQ?/td> | D6 D0 CE C4 | |
18 | IEҎ指定的编码或默认~码查看l果 | “中文”(正确昄Q?/td> | IE |
Alt+?当前行和下面一行交互位|?特别实用,可以省去先剪?再粘贴了)
Alt+?当前行和上面一行交互位|?同上)
Alt+?前一个编辑的面
Alt+?下一个编辑的面(当然是针对上面那条来说了)
Alt+Enter 昄当前选择资源(工程,or 文g or文g)的属?/p>
Shift+Enter 在当前行的下一行插入空?q时鼠标可以在当前行的Q一位置,不一定是最?
Shift+Ctrl+Enter 在当前行插入I(原理同上?
Ctrl+Q 定位到最后编辑的地方
Ctrl+L 定位在某?(对于E序过100的h有音?
Ctrl+M 最大化当前的Edit或View (再按则反?
Ctrl+/ 注释当前?再按则取消注?br />Ctrl+O 快速显C?OutLine
Ctrl+T 快速显C当前类的承结?br />Ctrl+W 关闭当前Editer
Ctrl+K 参照选中的Word快速定位到下一?br />Ctrl+E 快速显C当前Editer的下拉列?如果当前面没有昄的用黑体表示)
Ctrl+/(键? 折叠当前cM的所有代?/p>
Ctrl+×(键? 展开当前cM的所有代?/p>
Ctrl+Space 代码助手完成一些代码的插入(但一般和输入法有冲突,可以修改输入法的热键,也可以暂用Alt+/来代?
Ctrl+Shift+E 昄理当前打开的所有的View的管理器(可以选择关闭,Ȁzȝ操作)
Ctrl+J 正向增量查找(按下Ctrl+J?你所输入的每个字母编辑器都提供快速匹配定位到某个单词,如果没有,则在stutes line中显C没有找C,查一个单词时,特别实用,q个功能Idea两年前就有了)
Ctrl+Shift+J 反向增量查找(和上条相?只不q是从后往前查)
Ctrl+Shift+F4 关闭所有打开的Editer
Ctrl+Shift+X 把当前选中的文本全部变呛_?/p>
Ctrl+Shift+Y 把当前选中的文本全部变为小?/p>
Ctrl+Shift+F 格式化当前代?/p>
Ctrl+Shift+P 定位到对于的匚wW?譬如{}) (从前面定位后面时,光标要在匚wW里?后面到前?则反?
下面的快捷键是重构里面常用的,本hp己喜Ƣ且常用的整理一??一般重构的快捷键都是Alt+Shift开头的?
Alt+Shift+R 重命?(是我自己最q的一个了,其是变量和cȝRename,比手工方法能节省很多力_?
Alt+Shift+M 抽取Ҏ (q是重构里面最常用的方法之一?其是对一大堆泥团代码有用)
Alt+Shift+C 修改函数l构(比较实用,有N个函数调用了q个Ҏ,修改一ơ搞?
Alt+Shift+L 抽取本地变量( 可以直接把一些魔法数字和字符串抽取成一个变?其是多处调用的时?
Alt+Shift+F 把Class中的local变量变ؓfield变量 (比较实用的功?
Alt+Shift+I 合ƈ变量(可能q样说有点不妥Inline)
Alt+Shift+V Ud函数和变?不怎么常用)
Alt+Shift+Z 重构的后悔药(Undo)