# ~. w; O: |8 ]' i M, r (2)如果Unicode的16位字符的头5位是0,则用2个字节表示,首字节是“110”开头,后面的5位与源字符中除去头5个零后的最高5位相同;第二个字节以“10”开头,后面的6位与源字符中的低6位相同。如“\u025d”(0000 0010 0101 1101),转化后为“c99d”(1100 1001 1001 1101);: f7 z! e, e. ?0 O/ v
; c8 x/ ~1 v: T) O
(3)如果不符合上述两个规则,则用三个字节表示。第一个字节以“1110”开头,后四位为源字符的高四位;第二个字节以“10”开头,后六位为源字符中间的六位;第三个字节以“10”开头,后六位为源字符的低六位;如“\u9da7”(1001 1101 1010 0111),转化为“e9b6a7”(1110 1001 1011 0110 1010 0111); 2 J' c9 i' O c( b; D4 w ' [3 X7 o$ L/ T 可以这么描述JAVA程序中Unicode与UTF的关系,虽然不绝对:字符串在内存中运行时,表现为Unicode代码,而当要保存到文件或其它介质中去时,用的是UTF。这个转化过程是由writeUTF和readUTF来完成的。 8 z1 o& \3 C5 C# o0 V) _/ V/ }
好了,基础性的论述差不多了,下面进入正题。 " Y" k+ x/ N; X 8 |) w9 C% H, F+ e4 B9 q0 v 先把这个问题想成是一个黑匣子。先看黑匣子的一级表示:1 A% U' M* S) l
, y5 G. Y/ P3 J$ Q& \/ @input(charsetA)->process(Unicode)->output(charsetB) ' a7 q: m p" a( m$ B1 t9 v- D3 A( B" G" d
简单,这就是一个IPO模型,即输入、处理和输出。同样的内容要经过“从charsetA到unicode再到charsetB”的转化。 ! g& I5 A8 {; i0 [" J, m: G) ]" C5 c6 _8 E
再看二级表示:8 ^* U8 C! F# F5 L6 H
3 E2 s, O8 [% @5 ]1 ?# ]/ v7 FSourceFile(jsp,java)->class->output6 X1 `8 K* ^6 a
* d1 e: I `3 H2 r3 k5 O' C0 m
在这个图中,可以看出,输入的是jsp和java源文件,在处理过程中,以Class文件为载体,然后输出。再细化到三级表示: 8 Q/ l: d" Y5 x' k4 }! V$ p5 G2 O6 X) ~) c$ V
jsp->temp file->class->browser,os console,db; E2 O( ~: h/ L6 o7 K, W
3 N9 T) j, _. b4 s' n
app,servlet->class->browser,os console,db 5 u- j E7 X4 o+ f' O0 @) x- b + `0 F# _$ b' x) |2 j) x! f& m 这个图就更明白了。Jsp文件先生成中间的Java文件,再生成Class。而Servlet和普通App则直接编译生成Class。然后,从Class再输出到浏览器、控制台或数据库等。 ) f& [7 Q" {% c- ]: B % T( W$ p7 M% r9 F JSP:从源文件到Class的过程 + `: p0 f% X. B; S2 X; y3 U. b+ f2 h5 g, v- |2 L7 T! h+ N
Jsp的源文件是以“.jsp”结尾的文本文件。在本节中,将阐述JSP文件的解释和编译过程,并跟踪其中的中文变化。 7 N& X4 f- Z* H t; ~" y. ~# ^ 1 c7 j- n# O1 m2 P$ B 1、JSP/Servlet引擎提供的JSP转换工具(jspc)搜索JSP文件中用<%@ page contentType ="text/html; charset=<Jsp-charset>"%>中指定的charset。如果在JSP文件中未指定<Jsp-charset>,则取JVM中的默认设置file.encoding,一般情况下,这个值是ISO8859-1;% l9 m* B' t5 U. H ]9 A( K
- _7 O0 X' L6 _8 y. _- Y 2、jspc用相当于“javac –encoding <Jsp-charset>”的命令解释JSP文件中出现的所有字符,包括中文字符和ASCII字符,然后把这些字符转换成Unicode字符,再转化成UTF格式,存为JAVA文件。ASCII码字符转化为Unicode字符时只是简单地在前面加“00”,如“A”,转化为“\u0041”(不需要理由,Unicode的码表就是这么编的)。然后,经过到UTF的转换,又变回“41”了!这也就是可以使用普通文本编辑器查看由JSP生成的JAVA文件的原因;0 G0 _* s# V4 g. g3 a
, D) p. N* O" o2 T 3、引擎用相当于“javac –encoding UNICODE”的命令,把JAVA文件编译成CLASS文件;. L# ~- e1 T& `" K o% d
0 j. R' _7 D& @+ x, m) t* O 先看一下这些过程中中文字符的转换情况。有如下源代码: : K& L$ ?, A; }- S/ @2 b4 U 0 l( n3 X/ s" E4 y$ N/ e' g<%@ page contentType="text/html; charset=gb2312"%> 9 a6 w3 R5 e( H5 _! G<html><body>0 E3 C) }: W' t4 r3 `& l& q
<%( J" K! e6 x4 s3 o) p9 g5 A
String a="中文"; - Y4 O1 a- r: ]# d1 \ out.println(a); 4 V0 S5 G l1 B7 l* U# h! t' H%>; L# w* s( ]4 i8 ?
</body></html> K, \4 o6 H* f- E3 }# Y& a 这段代码是在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文件中的完全一样。 ) S2 n, |+ U- X8 m) ?" ^% ] : ?) A2 h; t8 |1 i) O7 A+ X 再看JSP中指定的CharSet为ISO-8859-1的情况。. R& F. Q5 w4 s& r
" J& |( N$ n \
5 J+ U2 @7 W6 C5 `' R/ p<%@ page contentType="text/html; charset=ISO-8859-1"%> $ l o _$ L( M<html><body> 9 X: B) `* M g<%0 r( F& o* W/ D
String a="中文"; 6 A/ u$ {2 H; S5 y out.println(a);4 _9 M2 \7 O- [+ U9 l
%> " b7 t) A' G* R. i7 p4 i. R* M</body></html>9 i" k% ^* [+ F5 ]' k% h# P/ [7 I$ r4 E4 n
同样,该文件是用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”。0 [# Y2 k3 q+ Q/ r
/ l8 t2 @6 B! T8 l; i5 D# }
如果上述代码中不指定<Jsp-charset>,即把第一行写成“<%@ page contentType="text/html" %>”,JSPC会使用file.encoding的设置来解释JSP文件。在RedHat 6.2上,其处理结果与指定为ISO-8859-1是完全相同的。 9 I, b# `% i4 c$ s( C# ^. J9 Q4 K. k' i8 U
到现在为止,已经解释了从JSP文件到CLASS文件的转变过程中中文字符的映射过程。一句话:从“JspCharSet到Unicode再到UTF”。下表总结了这个过程: y$ X/ A) L1 ~# p/ |# o9 g9 w% D. |
表2 “中文”从JSP到CLASS的转化过程 7 V$ \! G, B2 H* S# F0 Q8 \ # Z) }0 S, i; X/ ~) C4 E : I: J7 A, O, \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 C4; _1 J1 L3 G2 y& @+ K. b7 s
(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' H5 ^+ U& u$ X4 p
下节先讨论Servlet从JAVA文件到CLASS文件的转化过程,然后再解释从CLASS文件如何输出到客户端。之所以这样安排,是因为JSP和Servlet在输出时处理方法是一样的。
" ]) e: e1 q, \! t+ N2 H8 Z序号步骤说明结果1编写Servlet源文件,且存为GB2312格式D6 D0 CE C4+ | V l; ~" G" O/ }' ^
(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的页面编码为“简体中文”“中文”(正确显示) / T9 S/ W( w" ~0 p 如果不指定Compile-charset或Servlet-charset,其默认值均为ISO8859-1。) I* V8 G2 r+ y w# ~$ ^. }* c. H
3 Y/ H( \, P. P 当Compile-charset=Servlet-charset时,第2步和第4步能互逆,“抵消”,显示结果均能正确。读者可试着写一下Compile-charset<>Servlet-charset时的情况,肯定是不正确的。 $ J7 Q8 f+ y! \$ j1 ]1 ]3 J 4 |$ [ x# C8 [$ Z; P 当输出对象是数据库时 3 Z0 R" s( H! C" b4 \ |5 {$ u, W2 t8 \ S$ ]& M
输出到数据库时,原理与输出到浏览器也是一样的。本节只是Servlet为例,JSP的情况请读者自行推导。 " j3 \# h. B* }4 F- x3 Z' w7 @6 w1 s( r+ E6 W! b/ { o
假设有一个Servlet,它能接收来自客户端(IE,简体中文)的汉字字符串,然后把它写入到内码为ISO8859-1的数据库中,然后再从数据库中取出这个字符串,显示到客户端。5 h" R5 C# e9 f! {8 J# `
1 {0 j1 y8 T% l
表9 输出对象是数据库时的变化过程(1) 2 D5 R% q( _8 w+ ^: b1 p, Y7 J % J. N6 c) E2 h, M序号步骤说明结果域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 / J* E4 p2 D, [% }(Unicode) Servlet准备把字符串输出到客户端16Servlet根据<Servlet-charset>生成字节流D6D0 CE C4Servlet17Servlet把字节流输出到IE中,如果已指定<Servlet-charset>,还会设置IE的编码为<Servlet-charset>D6 D0 CE C418IE根据指定的编码或默认编码查看结果“中文”(正确显示)IE: n! r T5 u$ @% {) w3 q
解释一下,表中第4第5步和第15第16步是用红色标记的,表示要由编码者来作转换。第4、5两步其实就是一句话:“new String(source.getBytes("GB2312"), "ISO8859-1")”。第15、16两步也是一句话:“new String(source.getBytes("ISO8859-1"), "GB2312")”。亲爱的读者,你在这样编写代码时是否意识到了其中的每一个细节呢? ) ^$ K! `8 f% M9 a3 q; E+ O - i7 g. s( W! ]: s/ O/ w* d. O 至于客户端内码和数据库内码为其它值时的流程,和输出对象是系统控制台时的流程,请读者自己想吧。明白了上述流程的原理,相信你可以轻松地写出来。; L1 b0 Q: U6 ^, S
2 Q$ e! \( F r* B. O; v( m, M* o 行文至此,已可告一段落了。终点又回到了起点,对于编程者而言,几乎是什么影响都没有。 4 t/ T3 ] w A; b' f/ L( o9 _4 y X* s& v& q2 j3 F% O
因为我们早就被告之要这么做了。0 F, n8 H9 O+ ]$ {- q5 G