5 g: W8 T2 y, I: Z0 L5 z9 {2 T( f+ V 看看上面的例子,如果给一串Unicode编码“00D6 00D0 00CE 00C4”,如果不作转换,直接用Unicode码表来对照它时,是四个字符(而且是特殊字符);假如把它与“ISO8859-1”进行映射,则直接去掉前面的“00”即可得到“D6 D0 CE C4”,这是ASCII码表中的四个字符;而假如把它当作GB2312来进行映射,得到的结果很可能是一大堆乱码,因为在GB2312中有可能没有(也有可能有)字符与00D6等字符对应(如果对应不上,将得到0x3f,也就是问号,如果对应上了,由于00D6等字符太靠前,估计也是一些特殊符号,真正的汉字在Unicode中的编码从4E00开始)。 ) H+ {8 j2 {( {7 U, d 1 _6 E' x$ b! Y' U: d; Y; g 各位看到了,同样的Unicode字符,可以解释成不同的样子。当然,这其中有一种是我们期望的结果。以上例而论,“D6 D0 CE C4”应该是我们所想要的,当把“D6 D0 CE C4”输出到IE中时,用“简体中文”方式查看,就能看到清楚的“中文”两个字了。(当然了,如果你一定要用“西欧字符”来看,那也没办法,你将得不到任何有何时何地的东西)为什么呢?因为“00D6 00D0 00CE 00C4”本来就是由ISO8859-1转化过去的。7 S! ]/ i- c+ p$ p U B
/ R. q0 Z. N% Z9 F给出如下结论:, b; D. T7 _9 |. p4 `! ~" h: s: \( Q
+ ~/ X8 u' Y$ s `0 X( F! i
在Class输出字符串前,会将Unicode的字符串按照某一种内码重新生成字节流,然后把字节流输入,相当于进行了一步“String.getBytes(???)”操作。???代表某一种字符集。 5 Q3 d9 C. ~: F6 d n5 I5 M9 t/ a: x, f1 l2 A$ I. \
如果是Servlet,那么,这种内码就是在HttpServletResponse.setContentType()方法中指定的内码,也就是上文定义的<Servlet-charset>。& |3 s9 n" L, {; `5 @4 N) a
! W# R8 l$ J( f/ W 如果是JSP,那么,这种内码就是在<%@ page contentType=""%>中指定的内码,也就是上文定义的<Jsp-charset>。 ! C, J# p* D! d( q/ ` 4 a9 o7 m/ y/ X5 B- C 如果是Java程序,那么,这种内码就是file.encoding中指定的内码,默认为ISO8859-1。 D* `) [) B7 k: ]% ]. H5 ~1 c% s8 i. k3 Y. W: v6 Y6 |) F
当输出对象是浏览器时 , k9 F+ O! q3 B+ O $ M/ D- r( ^% \! t% _3 L) ~+ T 以流行的浏览器IE为例。IE支持多种内码。假如IE接收到了一个字节流“D6 D0 CE C4”,你可以尝试用各种内码去查看。你会发现用“简体中文”时能得到正确的结果。因为“D6 D0 CE C4”本来就是简体中文中“中文”两个字的编码。 * l3 v: f6 v8 N . h( I7 \! x' g OK,完整地看一遍。 2 f, |3 H; M0 o+ l- {% E' W/ Z9 ^7 v# [: _; ]9 G' |
JSP:源文件为GB2312格式的文本文件,且JSP源文件中有“中文”这两个汉字" G0 u3 ?# y1 s4 [8 _
1 p/ X$ M% M( N7 _5 W: v' o 如果指定了<Jsp-charset>为GB2312,转化过程如下表。3 m( ?! n+ Y } ?, `
- w! D$ M/ x9 S
表4 Jsp-charset = GB2312时的变化过程 6 ~2 c0 f, V u: V8 h 4 `6 b; ^" X! t2 O7 w) A& C序号步骤说明结果1编写JSP源文件,且存为GB2312格式D6 D0 CE C4 ' ?* u5 g- }9 R! o2 O* T, g* n! P(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用“简体中文”查看结果“中文”(正确显示) , M9 B$ Z% T1 f4 {+ l 如果指定了<Jsp-charset>为ISO8859-1,转化过程如下表。 0 _. g t8 N# F7 M0 }/ m0 Z8 l; R/ u( K
表5 Jsp-charset = ISO8859-1时的变化过程 $ `' n: H8 X8 X4 s+ N) ^9 E/ \% |! w) z/ N) p
序号步骤说明结果1编写JSP源文件,且存为GB2312格式D6 D0 CE C4/ E& g2 \) z& t' ^! z6 G. K9 `
(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 _' E( m- F* [" f7 i' }6 o% H(啥都不是!!!)5根据Jsp-charset=ISO8859-1把Unicode转化为字节流D6 D0 CE C46把字节流输出到IE中,并设置IE的编码为ISO8859-1(作者按:这个信息隐藏在HTTP头中)D6 D0 CE C47IE用“西欧字符”查看结果乱码,其实是四个ASCII字符,但由于大于128,所以显示出来的怪模怪样8改变IE的页面编码为“简体中文”“中文”(正确显示) $ U/ g) |! I& u; i$ \" @9 z; e) u1 c 奇怪了!为什么把<Jsp-charset>设成GB2312和ISO8859-1是一个样的,都能正确显示?因为表4表5中的第2步和第5步互逆,是相互“抵消”的。只不过当指定为ISO8859-1时,要增加第8步操作,殊为不便。' x+ n# E y+ @: M8 W0 J
( j/ p) w, \" o6 t9 G& n 再看看不指定<Jsp-charset> 时的情况。6 D, W+ k" Q: |; i
4 Q) q7 W. `% F2 J6 T, | 表6 未指定Jsp-charset 时的变化过程; E# K' s2 v% d6 G
3 `! ?6 r7 K6 p2 G- M! t1 w
序号步骤说明结果1编写JSP源文件,且存为GB2312格式D6 D0 CE C4 , f9 P) @- I- ?(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步 4 K# i8 E: @7 m8 N4 v" e/ `# m& B Servlet:源文件为JAVA文件,格式是GB2312,源文件中含有“中文”这两个汉字' r* K4 D4 J# ]9 I2 Q8 z
L b2 |8 p+ r' \
如果<Compile-charset>=GB2312,<Servlet-charset>=GB2312 ( D* e$ x" w; B7 N9 ^, A/ G 4 ^, ?# V3 d$ ?9 m8 X 表7 Compile-charset=Servlet-charset=GB2312 时的变化过程6 E1 S( L# ~) a5 W# ^
& @0 h/ R' A/ k) X+ m8 d
序号步骤说明结果1编写Servlet源文件,且存为GB2312格式D6 D0 CE C43 D1 w& {# d+ L2 Y
(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用“简体中文”查看结果“中文”(正确显示). p& G1 v% C% q
如果<Compile-charset>=ISO8859-1,<Servlet-charset>=ISO8859-1 ' d/ ]. g& h2 I9 ?4 ^( ]7 g9 { L6 @9 ^7 F6 Q0 Q% A W
表8 Compile-charset=Servlet-charset=ISO8859-1时的变化过程8 {" a+ e7 _4 @
& R! B6 n7 Z- ^$ q9 W; e
序号步骤说明结果1编写Servlet源文件,且存为GB2312格式D6 D0 CE C4 + {1 N: G* t" G9 H1 K(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的页面编码为“简体中文”“中文”(正确显示) 3 ]7 @6 e. q) y8 D+ s7 n 如果不指定Compile-charset或Servlet-charset,其默认值均为ISO8859-1。 + a* \8 H; f# S5 R3 F" X0 z7 o) [
当Compile-charset=Servlet-charset时,第2步和第4步能互逆,“抵消”,显示结果均能正确。读者可试着写一下Compile-charset<>Servlet-charset时的情况,肯定是不正确的。6 G4 A. x/ O% d8 t j
4 ~2 }; O! D; S2 J, P) m8 A O; a
当输出对象是数据库时 ( ?& q3 s+ {2 h5 j$ M s % M$ m! u! r3 } | 输出到数据库时,原理与输出到浏览器也是一样的。本节只是Servlet为例,JSP的情况请读者自行推导。 ; W7 t) U4 r/ A" U : }7 I8 @2 N9 c- [" @6 X* r/ t 假设有一个Servlet,它能接收来自客户端(IE,简体中文)的汉字字符串,然后把它写入到内码为ISO8859-1的数据库中,然后再从数据库中取出这个字符串,显示到客户端。 4 u' V8 o8 F( ^( J7 t! e 9 L0 O& ~1 l2 `$ k* \ 表9 输出对象是数据库时的变化过程(1) ' b( ], [) c$ i! Z, B0 b1 [4 Y8 J P% O8 j4 g$ [
序号步骤说明结果域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 4 _8 y5 |: M ?; n4 j6 ^0 c(Unicode) Servlet准备把字符串输出到客户端16Servlet根据<Servlet-charset>生成字节流D6D0 CE C4Servlet17Servlet把字节流输出到IE中,如果已指定<Servlet-charset>,还会设置IE的编码为<Servlet-charset>D6 D0 CE C418IE根据指定的编码或默认编码查看结果“中文”(正确显示)IE# V$ g( p+ i/ p: X5 d) f ]
解释一下,表中第4第5步和第15第16步是用红色标记的,表示要由编码者来作转换。第4、5两步其实就是一句话:“new String(source.getBytes("GB2312"), "ISO8859-1")”。第15、16两步也是一句话:“new String(source.getBytes("ISO8859-1"), "GB2312")”。亲爱的读者,你在这样编写代码时是否意识到了其中的每一个细节呢? 1 O8 S( T3 `' J+ w! X3 L* I - |$ X. f6 x1 `- ^1 ]. l 至于客户端内码和数据库内码为其它值时的流程,和输出对象是系统控制台时的流程,请读者自己想吧。明白了上述流程的原理,相信你可以轻松地写出来。- H$ v8 q' \5 n3 C
& P! C+ Y4 g( Y
行文至此,已可告一段落了。终点又回到了起点,对于编程者而言,几乎是什么影响都没有。/ N: |" K R5 [4 F: i9 X7 a
( D! |% t6 S. X# W+ t! o% @ 因为我们早就被告之要这么做了。 & V2 I u$ N% M# D: L " |! F1 g& a1 I) k- x( _2 t 以下给出一个结论,作为结尾。: U+ ~/ s( s# Z$ ?/ D7 K% v& @
! z0 w" H/ @6 _ 1、 在Jsp文件中,要指定contentType,其中,charset的值要与客户端浏览器所用的字符集一样;对于其中的字符串常量,不需做任何内码转换;对于字符串变量,要求能根据ContentType中指定的字符集还原成客户端能识别的字节流,简单地说,就是“字符串变量是基于<Jsp-charset>字符集的”;& T0 e: c) _; F6 M4 }, R
( ?# T; i2 A3 t 2、 在Servlet中,必须用HttpServletResponse.setContentType()设置charset,且设置成与客户端内码一致;对于其中的字符串常量,需要在Javac编译时指定encoding,这个encoding必须与编写源文件的平台的字符集一样,一般说来都是GB2312或GBK;对于字符串变量,与JSP一样,必须“是基于<Servlet-charset>字符集的”。