Servlet:从源文件到Class的过程 , R) ]# F: E6 F9 a! u- a1 Y5 L/ |$ t: x7 Z/ D6 I* [- J
Servlet源文件是以“.java”结尾的文本文件。本节将讨论Servlet的编译过程并跟踪其中的中文变化。 - B {& a7 ~3 q' T4 H6 Y' C6 b- H0 U* w8 n
用“javac”编译Servlet源文件。javac可以带“-encoding <Compile-charset>”参数,意思是“用< Compile-charset >中指定的编码来解释Serlvet源文件”。 & z! r$ V" I; x( m6 P3 V6 l7 _ % w+ ^% b% g3 i g$ O$ m5 }5 R 源文件在编译时,用<Compile-charset>来解释所有字符,包括中文字符和ASCII字符。然后把字符常量转变成Unicode字符,最后,把Unicode转变成UTF。 r2 ?+ G4 Y& ~6 O6 V4 s2 A8 c4 v; K, T, v
在Servlet中,还有一个地方设置输出流的CharSet。通常在输出结果前,调用HttpServletResponse的setContentType方法来达到与在JSP中设置<Jsp-charset>一样的效果,称之为<Servlet-charset>。 3 o% E; ~0 o1 _. R9 |% J( Q% D; k9 {* n, w5 T
注意,文中一共提到了三个变量:<Jsp-charset>、<Compile-charset>和<Servlet-charset>。其中,JSP文件只与<Jsp-charset>有关,而<Compile-charset>和<Servlet-charset>只与Servlet有关。 " J9 C4 H' i( p! t6 Q2 z: h4 R2 J& S7 J' n/ Z2 W. v& O
看下例: - t% K: ]; n7 ^$ d, G2 ]8 c! g( |0 e; P& `0 n0 l3 @6 ~
import javax.servlet.*; 1 S3 c3 `; y5 n $ b- J: `1 V4 L2 y- N" s2 Limport javax.servlet.http.*;/ d' K% g! Z; w* n# n+ @
( R: t. M: \) @: a" W1 ^* f6 Z
class testServlet extends HttpServlet- ^. N! u$ [+ Y- m0 M, q
{, }- }' T- l3 N, i' a
public void doGet(HttpServletRequest req,HttpServletResponse resp)% `/ U$ B, T8 |
throws ServletException,java.io.IOException2 s2 G; F h( Q
{* K; _# k( c1 F& I
resp.setContentType("text/html; charset=GB2312");" v1 S7 R2 T: m1 C; g$ i3 L
java.io.PrintWriter out=resp.getWriter();9 z( F; d5 y* Q8 V W: M" [
out.println("<html>");+ u& y0 l! ^" z5 E+ j; z" |; x
out.println("#中文#"); U6 b }! H* T0 Y3 s out.println("</html>"); ) f3 z l% c# r" U } 7 E) S' U8 B: ]% ?} ( A+ E m5 d) w( A$ S+ U 该文件也是用UltraEdit for Windows编写的,其中的“中文”两个字保存为“D6 D0 CE C4”(GB2312编码)。3 w3 h: {# Q' N7 S% @
8 P9 R9 i ~: c7 l2 ^' {: B
开始编译。下表是<Compile-charset>不同时,CLASS文件中“中文”两字的十六进制码。在编译过程中,<Servlet-charset>不起任何作用。<Servlet-charset>只对CLASS文件的输出产生影响,实际上是<Servlet-charset>和<Compile-charset>一起,达到与JSP文件中的<Jsp-charset>相同的效果,因为<Jsp-charset>对编译和CLASS文件的输出都会产生影响。 4 F- h S; a# |0 w1 S$ Y8 r7 A9 r6 ^2 E1 I4 Y2 l6 v+ `
表3 “中文”从Servlet源文件到Class的转变过程 , }) K1 o- j2 ^& o; ]7 z3 S, U7 N1 K+ i- K. s
Compile-charsetServlet源文件中Class文件中等效的Unicode码GB2312D6 D0 CE C4 ( @) l; f# t0 X$ Q0 m. x(GB2312)E4 B8 AD E6 96 87 (UTF)\u4E2D\u6587 (在Unicode中=“中文”)ISO-8859-1D6 D0 CE C4 5 m* `) ` R3 q
(GB2312)C3 96 C3 90 C3 8E C3 84 (UTF)\u00D6 \u00D0 \u00CE \u00C4 (在D6 D0 CE C4前面各加了一个00)无(默认)D6 D0 CE C4 (GB2312)同ISO-8859-1同ISO-8859-1$ E" o/ e+ v3 `% l( L# y+ Q
普通Java程序的编译过程与Servlet完全一样。 4 B) H$ [ @$ T+ z! t1 x ! s6 f7 V/ Z7 c9 q9 W1 V& x: L CLASS文件中的中文表示法是不是昭然若揭了?OK,接下来看看CLASS又是怎样输出中文的呢?, O4 [7 S' J, | v7 Q6 c$ F
% T0 ^: ?' `* i' f4 H/ G Class:输出字符串$ s- \; b1 x$ B
' l$ }( {% z( y* m. @" O( ?. y8 `6 {
上文说过,字符串在内存中表现为Unicode编码。至于这种Unicode编码表示了什么,那要看它是从哪种字符集映射过来的,也就是说要看它的祖先。这好比在托运行李时,外观都是纸箱子,里面装了什么就要看寄邮件的人实际邮了什么东西。 / _6 q. k4 `# Y) Q( x% E$ C7 g8 i3 q9 D @) d7 }
看看上面的例子,如果给一串Unicode编码“00D6 00D0 00CE 00C4”,如果不作转换,直接用Unicode码表来对照它时,是四个字符(而且是特殊字符);假如把它与“ISO8859-1”进行映射,则直接去掉前面的“00”即可得到“D6 D0 CE C4”,这是ASCII码表中的四个字符;而假如把它当作GB2312来进行映射,得到的结果很可能是一大堆乱码,因为在GB2312中有可能没有(也有可能有)字符与00D6等字符对应(如果对应不上,将得到0x3f,也就是问号,如果对应上了,由于00D6等字符太靠前,估计也是一些特殊符号,真正的汉字在Unicode中的编码从4E00开始)。 ) M1 [ X$ A) K# Y) | - G& l* Z: k! w' _. z- z9 k9 [ 各位看到了,同样的Unicode字符,可以解释成不同的样子。当然,这其中有一种是我们期望的结果。以上例而论,“D6 D0 CE C4”应该是我们所想要的,当把“D6 D0 CE C4”输出到IE中时,用“简体中文”方式查看,就能看到清楚的“中文”两个字了。(当然了,如果你一定要用“西欧字符”来看,那也没办法,你将得不到任何有何时何地的东西)为什么呢?因为“00D6 00D0 00CE 00C4”本来就是由ISO8859-1转化过去的。 q: a* r* x+ Y; f
: \* N3 T5 h' `% o, Y3 Q, S/ O给出如下结论: 0 N1 w2 A( {3 E4 \8 S* S5 M % U6 R/ l9 K) q+ q5 X0 K 在Class输出字符串前,会将Unicode的字符串按照某一种内码重新生成字节流,然后把字节流输入,相当于进行了一步“String.getBytes(???)”操作。???代表某一种字符集。 9 W: c) o; {6 X5 p; N. J- u- o3 `" s, `4 ^/ @7 ^% H/ s. j9 {
如果是Servlet,那么,这种内码就是在HttpServletResponse.setContentType()方法中指定的内码,也就是上文定义的<Servlet-charset>。 ( S- \6 o/ ]7 V3 V" R+ K9 l; ]: Y+ {$ {5 @7 P0 l' Z$ e
如果是JSP,那么,这种内码就是在<%@ page contentType=""%>中指定的内码,也就是上文定义的<Jsp-charset>。9 `/ I; U. ]; R
' ^1 ]+ ]. o% E/ k' @( A 如果是Java程序,那么,这种内码就是file.encoding中指定的内码,默认为ISO8859-1。' t& C9 A {: m9 h
( i' S6 i* ~0 v- F: u" J( }
当输出对象是浏览器时. t' j `& m$ h
/ J5 x8 `! ^4 d5 o, d 以流行的浏览器IE为例。IE支持多种内码。假如IE接收到了一个字节流“D6 D0 CE C4”,你可以尝试用各种内码去查看。你会发现用“简体中文”时能得到正确的结果。因为“D6 D0 CE C4”本来就是简体中文中“中文”两个字的编码。' Q- z$ h" m2 [3 W1 _3 a5 F
; Z/ m; T7 x2 s6 q" P: Y5 [ OK,完整地看一遍。 3 _4 T% z" L9 M, J2 r. _$ Y+ S . C: R" S; }$ ^1 I y4 |1 [ JSP:源文件为GB2312格式的文本文件,且JSP源文件中有“中文”这两个汉字2 K: l8 E; r+ p+ W6 i
/ i5 d! O7 O- u/ Z' h* d9 a: r/ J4 m 如果指定了<Jsp-charset>为GB2312,转化过程如下表。! F$ @! }+ h% E* F/ a
( k% c. r% t1 b/ }) c' }
表4 Jsp-charset = GB2312时的变化过程 3 a1 ~) y* `) P) D; X+ q ' p7 b: ^% c; E" w/ q序号步骤说明结果1编写JSP源文件,且存为GB2312格式D6 D0 CE C43 [ k) C& Z5 x$ L
(D6D0=中 CEC4=文)2jspc把JSP源文件转化为临时JAVA文件,并把字符串按照GB2312映射到Unicode,并用UTF格式写入JAVA文件中E4 B8 AD E6 96 873把临时JAVA文件编译成CLASS文件E4 B8 AD E6 96 874运行时,先从CLASS文件中用readUTF读出字符串,在内存中的是Unicode编码4E 2D 65 87(在Unicode中4E2D=中 6587=文)5根据Jsp-charset=GB2312把Unicode转化为字节流D6 D0 CE C46把字节流输出到IE中,并设置IE的编码为GB2312(作者按:这个信息隐藏在HTTP头中)D6 D0 CE C47IE用“简体中文”查看结果“中文”(正确显示)3 j" _! A- k% E) A
如果指定了<Jsp-charset>为ISO8859-1,转化过程如下表。8 `& X. a6 R, T A; B" ?- Y
9 P& b, p0 C( x6 W
表5 Jsp-charset = ISO8859-1时的变化过程 % Y7 ]0 O3 z) v9 q% y, J$ r& g6 o( y% o& M+ q+ I
序号步骤说明结果1编写JSP源文件,且存为GB2312格式D6 D0 CE C44 w2 Q4 i1 v( w$ f* t! N$ N
(D6D0=中 CEC4=文)2jspc把JSP源文件转化为临时JAVA文件,并把字符串按照ISO8859-1映射到Unicode,并用UTF格式写入JAVA文件中C3 96 C3 90 C3 8E C3 843把临时JAVA文件编译成CLASS文件C3 96 C3 90 C3 8E C3 844运行时,先从CLASS文件中用readUTF读出字符串,在内存中的是Unicode编码00 D6 00 D0 00 CE 00 C4 5 U( N9 Z3 A8 v3 X# C7 I, o1 Z0 t+ y(啥都不是!!!)5根据Jsp-charset=ISO8859-1把Unicode转化为字节流D6 D0 CE C46把字节流输出到IE中,并设置IE的编码为ISO8859-1(作者按:这个信息隐藏在HTTP头中)D6 D0 CE C47IE用“西欧字符”查看结果乱码,其实是四个ASCII字符,但由于大于128,所以显示出来的怪模怪样8改变IE的页面编码为“简体中文”“中文”(正确显示) : A: O5 i" p: \, W0 t. A- }6 Y; X 奇怪了!为什么把<Jsp-charset>设成GB2312和ISO8859-1是一个样的,都能正确显示?因为表4表5中的第2步和第5步互逆,是相互“抵消”的。只不过当指定为ISO8859-1时,要增加第8步操作,殊为不便。 F! k3 I. E+ x) i& u6 ?* `4 ^6 y( ~0 D$ q
再看看不指定<Jsp-charset> 时的情况。! g4 I4 M! {3 z/ E7 ^/ m3 Z! R
7 [( {3 j- V# V* z% F4 ]; y$ v
表6 未指定Jsp-charset 时的变化过程 - W4 |7 ^* s* g, D/ e % b0 Z/ N( m( e5 c# p序号步骤说明结果1编写JSP源文件,且存为GB2312格式D6 D0 CE C4% [1 {, i- D# f. g# e6 A
(D6D0=中 CEC4=文)2jspc把JSP源文件转化为临时JAVA文件,并把字符串按照ISO8859-1映射到Unicode,并用UTF格式写入JAVA文件中C3 96 C3 90 C3 8E C3 843把临时JAVA文件编译成CLASS文件C3 96 C3 90 C3 8E C3 844运行时,先从CLASS文件中用readUTF读出字符串,在内存中的是Unicode编码00 D6 00 D0 00 CE 00 C45根据Jsp-charset=ISO8859-1把Unicode转化为字节流D6 D0 CE C46把字节流输出到IE中D6 D0 CE C47IE用发出请求时的页面的编码查看结果视情况而定。如果是简体中文,则能正确显示,否则,需执行表5中的第8步- j u& F- Z H3 H0 T$ X; \9 M7 O
Servlet:源文件为JAVA文件,格式是GB2312,源文件中含有“中文”这两个汉字2 X# S4 ^% l( M5 s% [6 {1 d" \
i! k" W7 y* [- b
如果<Compile-charset>=GB2312,<Servlet-charset>=GB2312( C2 m- `/ t6 F t( L: v2 K
* v3 @8 {5 `0 f; _
表7 Compile-charset=Servlet-charset=GB2312 时的变化过程 , z6 |6 X3 R! R* ~0 A$ M2 r! F0 V; b u5 R' _/ @; p `序号步骤说明结果1编写Servlet源文件,且存为GB2312格式D6 D0 CE C4 ! ~6 R3 x" t2 D" W& e, Q/ x: s7 A+ [(D6D0=中 CEC4=文)2用javac –encoding GB2312把JAVA源文件编译成CLASS文件E4 B8 AD E6 96 87 (UTF)3运行时,先从CLASS文件中用readUTF读出字符串,在内存中的是Unicode编码4E 2D 65 87 (Unicode)4根据Servlet-charset=GB2312把Unicode转化为字节流D6 D0 CE C4 (GB2312)5把字节流输出到IE中并设置IE的编码属性为Servlet-charset=GB2312D6 D0 CE C4 (GB2312)6IE用“简体中文”查看结果“中文”(正确显示) # P0 E, }8 x$ |1 q& r 如果<Compile-charset>=ISO8859-1,<Servlet-charset>=ISO8859-18 l+ q) ~1 ?4 k, K2 U
$ A6 s( ~+ J( P6 Q' `) ~0 k
表8 Compile-charset=Servlet-charset=ISO8859-1时的变化过程 - m) a4 Z- K$ {) ]" ]% A" m ' p/ a0 M" _/ ^* S4 m, Q序号步骤说明结果1编写Servlet源文件,且存为GB2312格式D6 D0 CE C4. R- ^# a+ p" {' c
(D6D0=中 CEC4=文)2用javac –encoding ISO8859-1把JAVA源文件编译成CLASS文件C3 96 C3 90 C3 8E C3 84 (UTF)3运行时,先从CLASS文件中用readUTF读出字符串,在内存中的是Unicode编码00 D6 00 D0 00 CE 00 C44根据Servlet-charset=ISO8859-1把Unicode转化为字节流D6 D0 CE C45把字节流输出到IE中并设置IE的编码属性为Servlet-charset=ISO8859-1D6 D0 CE C4 (GB2312)6IE用“西欧字符”查看结果乱码(原因同表5)7改变IE的页面编码为“简体中文”“中文”(正确显示)" O3 o" T! I2 H @' l
如果不指定Compile-charset或Servlet-charset,其默认值均为ISO8859-1。 9 w: M- S* y2 g6 Z5 J1 M 9 U0 R E2 V! h+ s: G 当Compile-charset=Servlet-charset时,第2步和第4步能互逆,“抵消”,显示结果均能正确。读者可试着写一下Compile-charset<>Servlet-charset时的情况,肯定是不正确的。 9 U0 m4 `5 S- B% C* l: h; Q. q/ v5 |
当输出对象是数据库时) N( q$ S, F4 {