! ]" r" B3 `9 s) ?. q& Rimport javax.servlet.*; 3 c6 D! W: |1 T$ D8 B9 o! F$ _* h$ D/ c/ }' n* |% ?2 X
import javax.servlet.http.*; 9 Q+ {4 N6 `: T$ x3 v8 Z- L! T7 H, t1 O, X* y. C" c
class testServlet extends HttpServlet# d( r1 d v [: C5 f7 |6 c) [
{ 8 s& b$ o, T7 p) H public void doGet(HttpServletRequest req,HttpServletResponse resp) 1 G% Z$ T; g' f9 W- z3 T3 B r throws ServletException,java.io.IOException 2 Y! C/ |$ D: E+ O; D$ p6 s m {( K6 v' k9 L% v5 G0 V% l) r" p
resp.setContentType("text/html; charset=GB2312"); 9 H2 s! I6 t# \# K9 J1 F x java.io.PrintWriter out=resp.getWriter();9 Q9 i4 J1 S" ] O' I# A
out.println("<html>"); & Z* u! ]* v4 K0 r6 {, w8 g1 k' f out.println("#中文#");- D' [* G8 s; y: O' D$ F! b
out.println("</html>"); 3 w$ Q2 O1 D$ ?. V1 U }0 U0 `" {3 q# l, f' B
} : u# t8 _' G+ v3 S% z 该文件也是用UltraEdit for Windows编写的,其中的“中文”两个字保存为“D6 D0 CE C4”(GB2312编码)。. k0 P8 p" y' q% }% @
' Y$ V I. Q% X# V' D 开始编译。下表是<Compile-charset>不同时,CLASS文件中“中文”两字的十六进制码。在编译过程中,<Servlet-charset>不起任何作用。<Servlet-charset>只对CLASS文件的输出产生影响,实际上是<Servlet-charset>和<Compile-charset>一起,达到与JSP文件中的<Jsp-charset>相同的效果,因为<Jsp-charset>对编译和CLASS文件的输出都会产生影响。 9 \# [- g& Z, Y. H/ }, c! q + l# J6 V; A1 \; `( k( M 表3 “中文”从Servlet源文件到Class的转变过程; i6 W0 z! R4 H2 A3 C
1 j! X$ s1 w, S( o; K9 C3 qCompile-charsetServlet源文件中Class文件中等效的Unicode码GB2312D6 D0 CE C4 4 W7 Q1 i( i/ F0 j$ k
(GB2312)E4 B8 AD E6 96 87 (UTF)\u4E2D\u6587 (在Unicode中=“中文”)ISO-8859-1D6 D0 CE C4 4 k8 P ?/ H7 R6 H, k+ z" z
(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-12 {9 ~( J. R& ^: c% F$ H7 t
普通Java程序的编译过程与Servlet完全一样。 6 l7 v0 {1 @3 w7 G4 {- p8 r* U$ \8 E! f; a. M& ], p
CLASS文件中的中文表示法是不是昭然若揭了?OK,接下来看看CLASS又是怎样输出中文的呢? ( K; |$ V; d5 G9 Z& H/ M& V, \0 K- q" a* ^: C) R; Z. X: i; Z4 \
Class:输出字符串8 y* _7 \. r( e+ c
- t: } H& E/ ]6 g4 A( l+ u
上文说过,字符串在内存中表现为Unicode编码。至于这种Unicode编码表示了什么,那要看它是从哪种字符集映射过来的,也就是说要看它的祖先。这好比在托运行李时,外观都是纸箱子,里面装了什么就要看寄邮件的人实际邮了什么东西。 B0 d ~1 l+ L % B+ Q- I4 V. b# b; _! @/ s 看看上面的例子,如果给一串Unicode编码“00D6 00D0 00CE 00C4”,如果不作转换,直接用Unicode码表来对照它时,是四个字符(而且是特殊字符);假如把它与“ISO8859-1”进行映射,则直接去掉前面的“00”即可得到“D6 D0 CE C4”,这是ASCII码表中的四个字符;而假如把它当作GB2312来进行映射,得到的结果很可能是一大堆乱码,因为在GB2312中有可能没有(也有可能有)字符与00D6等字符对应(如果对应不上,将得到0x3f,也就是问号,如果对应上了,由于00D6等字符太靠前,估计也是一些特殊符号,真正的汉字在Unicode中的编码从4E00开始)。 $ E; t6 g, t( P; G+ l I% N- u$ s6 _! H8 U
各位看到了,同样的Unicode字符,可以解释成不同的样子。当然,这其中有一种是我们期望的结果。以上例而论,“D6 D0 CE C4”应该是我们所想要的,当把“D6 D0 CE C4”输出到IE中时,用“简体中文”方式查看,就能看到清楚的“中文”两个字了。(当然了,如果你一定要用“西欧字符”来看,那也没办法,你将得不到任何有何时何地的东西)为什么呢?因为“00D6 00D0 00CE 00C4”本来就是由ISO8859-1转化过去的。* T- W# c! O+ [1 o9 A
x n% u5 _' v9 s, R+ G
给出如下结论: ! r6 y$ V, m0 X4 T' w, B. h, k* w! t8 m( c0 L6 G
在Class输出字符串前,会将Unicode的字符串按照某一种内码重新生成字节流,然后把字节流输入,相当于进行了一步“String.getBytes(???)”操作。???代表某一种字符集。 ; w+ k% l8 X, M6 f$ p5 _ / S7 j; t1 U s2 R5 _ 如果是Servlet,那么,这种内码就是在HttpServletResponse.setContentType()方法中指定的内码,也就是上文定义的<Servlet-charset>。 % s+ a& G6 {9 X3 Z& P( o: z+ A! L3 h8 [
如果是JSP,那么,这种内码就是在<%@ page contentType=""%>中指定的内码,也就是上文定义的<Jsp-charset>。- I# H$ X, g- @+ w6 A. D
6 c( P8 l& [9 \! U5 T: K2 G, G% n 如果是Java程序,那么,这种内码就是file.encoding中指定的内码,默认为ISO8859-1。 : W/ \( r# Q* a7 m, m . D- D" a' o/ J% P 当输出对象是浏览器时& r" D5 k: j' e* |
' u8 W4 y% {6 a; [, U* l 以流行的浏览器IE为例。IE支持多种内码。假如IE接收到了一个字节流“D6 D0 CE C4”,你可以尝试用各种内码去查看。你会发现用“简体中文”时能得到正确的结果。因为“D6 D0 CE C4”本来就是简体中文中“中文”两个字的编码。: `$ F' m q. Q$ ]: z; G
8 E- q5 C# ~7 E+ M4 Z
OK,完整地看一遍。6 p6 y; g+ K* @+ o
) T, E7 e0 E( Y JSP:源文件为GB2312格式的文本文件,且JSP源文件中有“中文”这两个汉字 5 \8 r, ~" t" E- v1 t% O% T; j4 \0 |$ h
如果指定了<Jsp-charset>为GB2312,转化过程如下表。& V" b) a- ?) R0 b! b
+ S/ \$ T3 H+ Z6 K! Q2 K7 o
表4 Jsp-charset = GB2312时的变化过程 # ^! W: K! m1 c# K- @. l, j0 h7 Y% y3 D( l5 L) N
序号步骤说明结果1编写JSP源文件,且存为GB2312格式D6 D0 CE C4 - f4 \; _( g L3 ]7 t) T(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用“简体中文”查看结果“中文”(正确显示)0 a% N; b; z" w7 H3 \! i ~
如果指定了<Jsp-charset>为ISO8859-1,转化过程如下表。 " B% C8 u4 }6 s+ v% W$ g+ m* f( d" }: n* i. w* X' B% K- g- k
表5 Jsp-charset = ISO8859-1时的变化过程" B% Q0 r3 E! q) ?- q0 {
Y7 |% G- h; _4 ]
序号步骤说明结果1编写JSP源文件,且存为GB2312格式D6 D0 CE C4 " A2 Q7 v5 h9 o" l1 C: d(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 ! a9 B( U# s1 S+ b2 V7 D2 J9 ~(啥都不是!!!)5根据Jsp-charset=ISO8859-1把Unicode转化为字节流D6 D0 CE C46把字节流输出到IE中,并设置IE的编码为ISO8859-1(作者按:这个信息隐藏在HTTP头中)D6 D0 CE C47IE用“西欧字符”查看结果乱码,其实是四个ASCII字符,但由于大于128,所以显示出来的怪模怪样8改变IE的页面编码为“简体中文”“中文”(正确显示)+ O! X+ e8 V% P w7 B
奇怪了!为什么把<Jsp-charset>设成GB2312和ISO8859-1是一个样的,都能正确显示?因为表4表5中的第2步和第5步互逆,是相互“抵消”的。只不过当指定为ISO8859-1时,要增加第8步操作,殊为不便。 * T9 m5 Y6 m( c8 M2 d+ Z- k! y1 X# E8 Z! P- d* y
再看看不指定<Jsp-charset> 时的情况。 + Q" Q3 W8 t& R8 P n0 L' H. |! j |
表6 未指定Jsp-charset 时的变化过程 # I: `% q* k) \ G - @; z5 B8 l2 g序号步骤说明结果1编写JSP源文件,且存为GB2312格式D6 D0 CE C4 2 g) i5 `' T8 o- t/ ?(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步 " T6 T0 [+ K3 m% o" T Servlet:源文件为JAVA文件,格式是GB2312,源文件中含有“中文”这两个汉字 ( k$ |2 p% p4 G1 M ( p1 K* d/ @8 O2 w& g: Q5 ~! h: d 如果<Compile-charset>=GB2312,<Servlet-charset>=GB2312 % L( C6 x* {3 t8 E. _; `: {( Z 0 ^, e. ?/ ]3 U& w 表7 Compile-charset=Servlet-charset=GB2312 时的变化过程 $ v5 ?/ E9 f' G' b3 d7 h3 B5 a' b$ u& e* S; q- b
序号步骤说明结果1编写Servlet源文件,且存为GB2312格式D6 D0 CE C49 m! [# p7 T0 o
(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用“简体中文”查看结果“中文”(正确显示)7 l6 x" f# i" T4 o! d
如果<Compile-charset>=ISO8859-1,<Servlet-charset>=ISO8859-1 % [& e, a7 ]% _, A( w! n6 i ) i1 S( q# k3 I5 D3 P 表8 Compile-charset=Servlet-charset=ISO8859-1时的变化过程 7 q) s/ N- p3 t* p: ^" j3 q ' p3 e: A* z( _6 [( z! W序号步骤说明结果1编写Servlet源文件,且存为GB2312格式D6 D0 CE C4 % X' U% Y' a$ e9 R5 m(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的页面编码为“简体中文”“中文”(正确显示) . w' q1 x& x$ ]1 A+ _2 G 如果不指定Compile-charset或Servlet-charset,其默认值均为ISO8859-1。! B$ v# Z! E& d7 J4 V& r
& E, k% A9 Q4 U7 N% y. {+ F4 f! r' P/ g 当Compile-charset=Servlet-charset时,第2步和第4步能互逆,“抵消”,显示结果均能正确。读者可试着写一下Compile-charset<>Servlet-charset时的情况,肯定是不正确的。 5 ^8 K; M. j6 a: ~8 y* k4 Y ?7 n% o2 A, V+ g( g/ \
当输出对象是数据库时) e3 i d$ J* ~( b
- S! D) ]7 S$ F9 W0 J6 e& Y! d
输出到数据库时,原理与输出到浏览器也是一样的。本节只是Servlet为例,JSP的情况请读者自行推导。 ( i9 \: Q; B I+ m8 ^) I5 K& Q3 ` P' P+ x
假设有一个Servlet,它能接收来自客户端(IE,简体中文)的汉字字符串,然后把它写入到内码为ISO8859-1的数据库中,然后再从数据库中取出这个字符串,显示到客户端。) {1 z- E! c5 i6 {7 c) R
* V# }5 ?4 t. E 表9 输出对象是数据库时的变化过程(1)0 ]/ D! j# H' _ y/ z' x; [
" G% ]/ A8 `9 ~# y& v
序号步骤说明结果域1在IE中输入“中文”D6 D0 CE C4IE2IE把字符串转变成UTF,并送入传输流中E4 B8 AD E6 96 873Servlet接收到输入流,用readUTF读取4E 2D 65 87(unicode)Servlet4编程者在Servlet中必须把字符串根据GB2312还原为字节流D6 D0 CE C45编程者根据数据库内码ISO8859-1生成新的字符串00 D6 00 D0 00 CE 00 C46把新生成的字符串提交给JDBC00 D6 00 D0 00 CE 00 C47JDBC检测到数据库内码为ISO8859-100 D6 00 D0 00 CE 00 C4JDBC8JDBC把接收到的字符串按照ISO8859-1生成字节流D6 D0 CE C49JDBC把字节流写入数据库中D6 D0 CE C410完成数据存储工作D6 D0 CE C4 数据库以下是从数据库中取出数的过程11JDBC从数据库中取出字节流D6 D0 CE C4JDBC12JDBC按照数据库的字符集ISO8859-1生成字符串,并提交给Servlet00 D6 00 D0 00 CE 00 C4 (Unicode) 13Servlet获得字符串00 D6 00 D0 00 CE 00 C4 (Unicode)Servlet14编程者必须根据数据库的内码ISO8859-1还原成原始字节流D6 D0 CE C4 15编程者必须根据客户端字符集GB2312生成新的字符串4E 2D 65 87 , G0 _0 l& t2 @& N3 [(Unicode) Servlet准备把字符串输出到客户端16Servlet根据<Servlet-charset>生成字节流D6D0 CE C4Servlet17Servlet把字节流输出到IE中,如果已指定<Servlet-charset>,还会设置IE的编码为<Servlet-charset>D6 D0 CE C418IE根据指定的编码或默认编码查看结果“中文”(正确显示)IE. G4 \. T; y; o8 K
解释一下,表中第4第5步和第15第16步是用红色标记的,表示要由编码者来作转换。第4、5两步其实就是一句话:“new String(source.getBytes("GB2312"), "ISO8859-1")”。第15、16两步也是一句话:“new String(source.getBytes("ISO8859-1"), "GB2312")”。亲爱的读者,你在这样编写代码时是否意识到了其中的每一个细节呢?2 b1 a0 u0 ^" l- d8 ~; V4 `
5 T \8 @' Q1 P" i
至于客户端内码和数据库内码为其它值时的流程,和输出对象是系统控制台时的流程,请读者自己想吧。明白了上述流程的原理,相信你可以轻松地写出来。 / U* g( K8 L, [ ( s5 D- F7 c% f! y9 ?6 ^; t9 ^9 T 行文至此,已可告一段落了。终点又回到了起点,对于编程者而言,几乎是什么影响都没有。 % k) o0 m/ p# |7 x6 z/ b' v0 U8 E. B* M4 P" k9 D" u% [
因为我们早就被告之要这么做了。 : L8 {3 W6 E+ P! Z& d) [ 6 l1 b7 i% g9 x; V& f2 a- O 以下给出一个结论,作为结尾。 2 X0 h7 N% H/ T1 d5 {# A1 [1 }0 j6 j6 j; C9 D
1、 在Jsp文件中,要指定contentType,其中,charset的值要与客户端浏览器所用的字符集一样;对于其中的字符串常量,不需做任何内码转换;对于字符串变量,要求能根据ContentType中指定的字符集还原成客户端能识别的字节流,简单地说,就是“字符串变量是基于<Jsp-charset>字符集的”;7 Z' f. m5 Z6 L, h6 ^* s" B) d
2 u6 d0 w2 i% w0 W1 ^, y
2、 在Servlet中,必须用HttpServletResponse.setContentType()设置charset,且设置成与客户端内码一致;对于其中的字符串常量,需要在Javac编译时指定encoding,这个encoding必须与编写源文件的平台的字符集一样,一般说来都是GB2312或GBK;对于字符串变量,与JSP一样,必须“是基于<Servlet-charset>字符集的”。