! g6 L( O1 ^* b 上文说过,异种语言之间的转换是通过Unicode来完成的。假设有两种不同的语言A和B,转换的步骤为:先把A转化为Unicode,再把Unicode转化为B。" I: J% e$ M a6 G# i. z6 Y5 ~
" s+ S5 p: Y q- O
举例说明。有GB2312中有一个汉字“李”,其编码为“C0EE”,欲转化为ISO8859-1编码。步骤为:先把“李”字转化为Unicode,得到“674E”,再把“674E”转化为ISO8859-1字符。当然,这个映射不会成功,因为ISO8859-1中根本就没有与“674E”对应的字符。 % h1 m; [; \5 Y: t3 T / F D1 X( s3 c7 H- y' [# F 当映射不成功时,问题就发生了!当从某语言向Unicode转化时,如果在某语言中没有该字符,得到的将是Unicode的代码“\uffffd”(“\u”表示是Unicode编码,)。而从Unicode向某语言转化时,如果某语言没有对应的字符,则得到的是“0x3f”(“?”)。这就是“?”的由来。) k! b- b% l, d6 z3 \; \
, l2 l% z8 h! g. G$ C7 G 例如:把字符流buf =“0x80 0x40 0xb0 0xa1”进行new String(buf, "gb2312")操作,得到的结果是“\ufffd\u554a”,再println出来,得到的结果将是“?啊”,因为“0x80 0x40”是GBK中的字符,在GB2312中没有。8 b! g8 G+ M9 I9 O- X$ ]) m
3 B1 R$ _# {) o) E5 _8 a" b* }0 ~) j 再如,把字符串String="\u00d6\u00ec\u00e9\u0046\u00bb\u00f9"进行new String (buf.getBytes("GBK"))操作,得到的结果是“3fa8aca8a6463fa8b4”,其中,“\u00d6”在“GBK”中没有对应的字符,得到“3f”,“\u00ec”对应着“a8ac”,“\u00e9”对应着“a8a6”,“0046”对应着“46”(因为这是ASCII字符),“\u00bb”没找到,得到“3f”,最后,“\u00f9”对应着“a8b4”。把这个字符串println一下,得到的结果是“?ìéF?ù”。看到没?这里并不全是问号,因为GBK与Unicode映射的内容中除了汉字外还有字符,本例就是最好的明证。 ) O' l2 |3 }; Y& p+ X v. A* E- {: m
所以,在汉字转码时,如果发生错乱,得到的不一定都是问号噢!不过,错了终究是错了,50步和100步并没有质的差别。1 B3 ^5 @5 |9 k
# T+ r0 L' p$ X i1 @
或者会问:如果源字符集中有,而Unicode中没有,结果会如何?回答是不知道。因为我手头没有能做这个测试的源字符集。但有一点是肯定的,那就是源字符集不够规范。在Java中,如果发生这种情况,是会抛出异常的。 1 K7 O d0 t. o: G, _; T. f& a5 [/ R8 A, Z, A0 O0 n G/ W4 Y6 | T- `
什么是UTF, g; }8 G$ \8 h' T( P) g$ ^
Q$ W" {# q8 Q6 k0 `: d# E: z
UTF,是Unicode Text Format的缩写,意为Unicode文本格式。对于UTF,是这样定义的: ; Q6 ^; j- m/ c5 i : a6 Q. d t; I5 y# Q5 E* ~ (1)如果Unicode的16位字符的头9位是0,则用一个字节表示,这个字节的首位是“0”,剩下的7位与原字符中的后7位相同,如“\u0034”(0000 0000 0011 0100),用“34” (0011 0100)表示;(与源Unicode字符是相同的);: n/ s' R) L) B) u3 e" x
% m; F" c: m5 A
(2)如果Unicode的16位字符的头5位是0,则用2个字节表示,首字节是“110”开头,后面的5位与源字符中除去头5个零后的最高5位相同;第二个字节以“10”开头,后面的6位与源字符中的低6位相同。如“\u025d”(0000 0010 0101 1101),转化后为“c99d”(1100 1001 1001 1101);: q( h# Z/ c5 {# Z n' B7 w# m
* K2 p" `0 k. l3 _
(3)如果不符合上述两个规则,则用三个字节表示。第一个字节以“1110”开头,后四位为源字符的高四位;第二个字节以“10”开头,后六位为源字符中间的六位;第三个字节以“10”开头,后六位为源字符的低六位;如“\u9da7”(1001 1101 1010 0111),转化为“e9b6a7”(1110 1001 1011 0110 1010 0111);6 a) ]" E$ N( k" o- [. S8 ] [
* x6 v Z* ]) V: {- v% b" d S4 a 可以这么描述JAVA程序中Unicode与UTF的关系,虽然不绝对:字符串在内存中运行时,表现为Unicode代码,而当要保存到文件或其它介质中去时,用的是UTF。这个转化过程是由writeUTF和readUTF来完成的。+ E! U% @/ `: F) X6 C' U
' A; r* `$ n8 o7 V/ K" `) b5 h 好了,基础性的论述差不多了,下面进入正题。2 B1 k& X' ?" r& e
' U0 j3 O/ a: D. L
先把这个问题想成是一个黑匣子。先看黑匣子的一级表示: - ~5 S; r3 x2 Z8 r0 @6 o6 \/ T- Q* r" L% y. {6 L# i
input(charsetA)->process(Unicode)->output(charsetB) 3 \; i/ k! O' R+ H1 Z0 M% v4 h$ }; C" U4 C& p U
简单,这就是一个IPO模型,即输入、处理和输出。同样的内容要经过“从charsetA到unicode再到charsetB”的转化。 ) R8 k7 g7 H0 w4 a, u7 A& { + n; |8 }8 p- z3 @ 再看二级表示:' |* m! H/ j5 X M% o( y! s
" c2 c; J; x+ Q1 M- Y- d( ]1 v
SourceFile(jsp,java)->class->output, m$ q5 ~$ b8 x( E
* B' W6 n4 R. m9 y1 X5 U) @ 在这个图中,可以看出,输入的是jsp和java源文件,在处理过程中,以Class文件为载体,然后输出。再细化到三级表示:6 c, B+ k) |5 P
! K1 r/ K0 n4 t# |4 I& yjsp->temp file->class->browser,os console,db / P) G! A$ S% _6 T . u0 \8 f& P. J! m$ n) J/ z! ~app,servlet->class->browser,os console,db. j' f) s& | G/ X1 K6 ^; y
; B. J; x; J% U2 Y7 m& O1 z
这个图就更明白了。Jsp文件先生成中间的Java文件,再生成Class。而Servlet和普通App则直接编译生成Class。然后,从Class再输出到浏览器、控制台或数据库等。 9 T2 a g+ l2 q! S ( c, w3 q$ h) `& Q: c5 }2 P JSP:从源文件到Class的过程 J5 ?, ~. ?) ^5 h
2 P% ^1 d8 j1 k2 K1 l* A' u7 Z' o
Jsp的源文件是以“.jsp”结尾的文本文件。在本节中,将阐述JSP文件的解释和编译过程,并跟踪其中的中文变化。 + {9 F& K" { f* w! h' ]! W6 C& G$ X6 }3 Z/ ?+ R) A
1、JSP/Servlet引擎提供的JSP转换工具(jspc)搜索JSP文件中用<%@ page contentType ="text/html; charset=<Jsp-charset>"%>中指定的charset。如果在JSP文件中未指定<Jsp-charset>,则取JVM中的默认设置file.encoding,一般情况下,这个值是ISO8859-1; % C, x# Z+ T$ G* a8 l; e4 M; U" _% S u. l$ ~
2、jspc用相当于“javac –encoding <Jsp-charset>”的命令解释JSP文件中出现的所有字符,包括中文字符和ASCII字符,然后把这些字符转换成Unicode字符,再转化成UTF格式,存为JAVA文件。ASCII码字符转化为Unicode字符时只是简单地在前面加“00”,如“A”,转化为“\u0041”(不需要理由,Unicode的码表就是这么编的)。然后,经过到UTF的转换,又变回“41”了!这也就是可以使用普通文本编辑器查看由JSP生成的JAVA文件的原因; 7 F, {% ]2 e4 b0 H 5 t- ]2 ~- g, p0 J. g9 E4 w 3、引擎用相当于“javac –encoding UNICODE”的命令,把JAVA文件编译成CLASS文件;3 U4 E8 b S8 I M7 E" T
- [8 u) w$ f6 k 先看一下这些过程中中文字符的转换情况。有如下源代码: / E, K# G) h) R$ {3 P! E. y) H1 `& g: h9 p6 R/ y" M0 f- r( z- W6 ~
<%@ page contentType="text/html; charset=gb2312"%>8 [8 K- }/ N: \
<html><body> 0 u% N5 e$ a7 }3 w, m6 { P+ u+ k<% 5 b& @# b$ k: d: [) @6 E5 H9 J String a="中文";2 v: V" s$ B f
out.println(a); # W- q2 b. G5 e+ v3 Q9 h1 Z- B%>- k# I& q4 `0 }5 G+ v- R2 f5 z
</body></html>+ ` a. t" O5 t2 u* I
这段代码是在UltraEdit for Windows上编写的。保存后,“中文”两个字的16进制编码为“D6 D0 CE C4”(GB2312编码)。经查表,“中文”两字的Unicode编码为“\u4E2D\u6587”,用 UTF表示就是“E4 B8 AD E6 96 87”。打开引擎生成的由JSP文件转变而成的JAVA文件,发现其中的“中文”两个字确实被“E4 B8 AD E6 96 87”替代了,再查看由JAVA文件编译生成的CLASS文件,发现结果与JAVA文件中的完全一样。+ P) F1 X& U0 p% Z. ]0 b
0 p! q g' K+ U# ?1 x
再看JSP中指定的CharSet为ISO-8859-1的情况。7 v A. y* D# o% u3 ]5 g9 R+ y0 V( L
# D2 f- ?. r" A" v - G/ }7 [' V6 V<%@ page contentType="text/html; charset=ISO-8859-1"%> 8 v7 B$ O" D/ O1 m! l, Y<html><body> 7 v) j; w8 Z8 @$ v# O<%: }" j9 ?1 K$ Q
String a="中文";8 ^- H) _! Q7 W- ]% T1 c
out.println(a);3 d& \4 w J9 C4 c7 k ?& n
%> & @* p/ ?- o9 X</body></html>; ?; B9 X- D* c
同样,该文件是用UltraEdit编写的,“中文”这两个字也是存为GB2312编码“D6 D0 CE C4”。先模拟一下生成的JAVA文件和CLASS文件的过程:jspc用ISO-8859-1来解释“中文”,并把它映射到Unicode。由于ISO-8859-1是8位的,且是拉丁语系,其映射规则就是在每个字节前加“00”,所以,映射后的Unicode编码应为“\u00D6\u00D0\u00CE\u00C4”,转化成UTF后应该是“C3 96 C3 90 C3 8E C3 84”。好,打开文件看一下,JAVA文件和CLASS文件中,“中文”果然都表示为“C3 96 C3 90 C3 8E C3 84”。 ! I) O. J+ N; ]7 H# p9 Q6 q$ e- j' e ! W$ {1 B: H/ L7 l( Z 如果上述代码中不指定<Jsp-charset>,即把第一行写成“<%@ page contentType="text/html" %>”,JSPC会使用file.encoding的设置来解释JSP文件。在RedHat 6.2上,其处理结果与指定为ISO-8859-1是完全相同的。$ t! t+ S; o+ @$ t7 u L
2 J/ v e# Y' y
到现在为止,已经解释了从JSP文件到CLASS文件的转变过程中中文字符的映射过程。一句话:从“JspCharSet到Unicode再到UTF”。下表总结了这个过程: 7 w4 l$ ?, N: w% Q5 F 2 X2 R8 p4 }, i- x1 `, y, R 表2 “中文”从JSP到CLASS的转化过程 9 _8 J: A5 b% T( I3 N $ a) k* k: h. |4 v5 ?4 f" K5 E: G9 s& Q6 Q$ r2 R6 e' }+ }# y( \) ~
Jsp-CharSetJSP文件中JAVA文件中CLASS文件中GB2312D6 D0 CE C4(GB2312)从\u4E2D\u6587(Unicode)到E4 B8 AD E6 96 87 (UTF)E4 B8 AD E6 96 87 (UTF)ISO-8859-1D6 D0 CE C40 s q: q+ }" V' Q& w
(GB2312)从\u00D6\u00D0\u00CE\u00C4 (Unicode)到C3 96 C3 90 C3 8E C3 84 (UTF)C3 96 C3 90 C3 8E C3 84 (UTF)无(默认=file.encoding)同ISO-8859-1同ISO-8859-1同ISO-8859-1 1 l, Z7 Y" B# A# z 下节先讨论Servlet从JAVA文件到CLASS文件的转化过程,然后再解释从CLASS文件如何输出到客户端。之所以这样安排,是因为JSP和Servlet在输出时处理方法是一样的。