QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 2706|回复: 1
打印 上一主题 下一主题

深入剖析JSP和Servlet对中文的处理

[复制链接]
字体大小: 正常 放大
韩冰        

823

主题

3

听众

4048

积分

我的地盘我做主

该用户从未签到

发帖功臣 元老勋章

跳转到指定楼层
1#
发表于 2004-11-21 12:02 |只看该作者 |正序浏览
|招呼Ta 关注Ta
世界上的各地区都有本地的语言。地区差异直接导致了语言环境的差异。在开发一个国际化程序的过程中,处理语言问题就显得很重要了。* P1 ]0 U9 _5 F$ f' d

' _2 g5 K' b# R0 u! m0 j  q6 A  这是一个世界范围内都存在的问题,所以,Java提供了世界性的解决方法。本文描述的方法是用于处理中文的,但是,推而广之,对于处理世界上其它国家和地区的语言同样适用。
! b4 @$ u9 n5 W2 Q8 p" R% v) i, {
$ F5 T- V/ R$ G4 [3 W  汉字是双字节的。所谓双字节是指一个双字要占用两个BYTE的位置(即16位),分别称为高位和低位。中国规定的汉字编码为GB2312,这是强制性的,目前几乎所有的能处理中文的应用程序都支持GB2312。GB2312包括了一二级汉字和9区符号,高位从0xa1到0xfe,低位也是从0xa1到0xfe,其中,汉字的编码范围为0xb0a1到0xf7fe。
. |6 |5 o! N! F" s* |6 E
- [8 ?5 Z, |7 c4 t, K  另外有一种编码,叫做GBK,但这是一份规范,不是强制的。GBK提供了20902个汉字,它兼容GB2312,编码范围为0x8140到0xfefe。GBK中的所有字符都可以一一映射到Unicode 2.0。2 a' M- @# h, |5 O

1 d. W0 O' W5 m& [8 C% P  在不久的将来,中国会颁布另一种标准:GB18030-2000(GBK2K)。它收录了藏、蒙等少数民族的字型,从根本上解决了字位不足的问题。注意:它不再是定长的。其二字节部份与GBK兼容,四字节部分是扩充的字符、字形。它的首字节和第三字节从0x81到0xfe,二字节和第四字节从0x30到0x39。% a: X% U0 x" c7 X% y' [/ S, ?

7 a; s- `2 j: L( c. Y  本文不打算介绍Unicode,有兴趣的可以浏览“http://www.unicode.org/”查看更多的信息。Unicode有一个特性:它包括了世界上所有的字符字形。所以,各个地区的语言都可以建立与Unicode的映射关系,而Java正是利用了这一点以达到异种语言之间的转换。/ j& g6 z" `7 L2 s$ J; Z

# u7 B; W+ ^$ Y# g' L/ n" `! t  在JDK中,与中文相关的编码有:. G, t, _' R! A4 A! R! G

5 u. d2 t9 ]7 |: h1 s$ A  X3 e* M  表1 JDK中与中文相关的编码列表
2 Q9 H* ?8 s6 e$ |% k
) l! m" b6 d7 U$ b/ R% \编码名称说明ASCII7位,与ascii7相同ISO8859-18-位,与 8859_1,ISO-8859-1,ISO_8859-1,latin1...等相同GB2312-8016位,与gb2312,gb2312-1980,EUC_CN,euccn,1381,Cp1381, 1383, Cp1383, ISO2022CN,ISO2022CN_GB...等相同GBK与MS936相同,注意:区分大小写UTF8与UTF-8相同GB18030与cp1392、1392相同,目前支持的JDK很少& U6 l6 u- r4 t0 C; k& p
  在实际编程时,接触得比较多的是GB2312(GBK)和ISO8859-1。
. U% l" p7 o5 H6 [% O3 T; g# U- J( \
  为什么会有“?”号
' P4 o3 q; T* r2 n) E" o5 r* z& y' O
8 n. i4 x/ j9 q4 L0 u5 S  上文说过,异种语言之间的转换是通过Unicode来完成的。假设有两种不同的语言A和B,转换的步骤为:先把A转化为Unicode,再把Unicode转化为B。( ]# J1 S. u& k- t& f; F. [7 E: m, b$ ]
0 j6 C% |2 {" }) s' D
  举例说明。有GB2312中有一个汉字“李”,其编码为“C0EE”,欲转化为ISO8859-1编码。步骤为:先把“李”字转化为Unicode,得到“674E”,再把“674E”转化为ISO8859-1字符。当然,这个映射不会成功,因为ISO8859-1中根本就没有与“674E”对应的字符。
0 T7 m2 }& v4 _1 W
, J2 z, V: a2 R+ D! e  当映射不成功时,问题就发生了!当从某语言向Unicode转化时,如果在某语言中没有该字符,得到的将是Unicode的代码“\uffffd”(“\u”表示是Unicode编码,)。而从Unicode向某语言转化时,如果某语言没有对应的字符,则得到的是“0x3f”(“?”)。这就是“?”的由来。
! P. q2 w- x5 X& }% q+ v; V9 z6 n1 |
  例如:把字符流buf =“0x80 0x40 0xb0 0xa1”进行new String(buf, "gb2312")操作,得到的结果是“\ufffd\u554a”,再println出来,得到的结果将是“?啊”,因为“0x80 0x40”是GBK中的字符,在GB2312中没有。' K/ F+ u9 }& N, @- w
" _8 n$ S! l7 m1 H# s- @$ H
  再如,把字符串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映射的内容中除了汉字外还有字符,本例就是最好的明证。2 W: R5 [+ m1 \& q5 [0 u

1 |" @7 R1 @* i; A. m1 z  所以,在汉字转码时,如果发生错乱,得到的不一定都是问号噢!不过,错了终究是错了,50步和100步并没有质的差别。" O& p2 A  B3 @7 h( j

* V  {& |  y( n# m  或者会问:如果源字符集中有,而Unicode中没有,结果会如何?回答是不知道。因为我手头没有能做这个测试的源字符集。但有一点是肯定的,那就是源字符集不够规范。在Java中,如果发生这种情况,是会抛出异常的。
% U* o/ ~( ?  x: n. Z2 ~" F2 N
/ h2 V/ x. ?" E/ k5 {! Q& v- C什么是UTF4 B5 P  {+ C- Z. `* ?

. ?% c/ J! W- t: C! R9 p0 e  UTF,是Unicode Text Format的缩写,意为Unicode文本格式。对于UTF,是这样定义的:
% V" d3 l% G6 p/ D) S, ]
, W* K, y% h1 Z' Y( m7 M  (1)如果Unicode的16位字符的头9位是0,则用一个字节表示,这个字节的首位是“0”,剩下的7位与原字符中的后7位相同,如“\u0034”(0000 0000 0011 0100),用“34” (0011 0100)表示;(与源Unicode字符是相同的);
4 [! M# t% y& ]  |# s/ |. k8 v* l' n0 g3 O
  (2)如果Unicode的16位字符的头5位是0,则用2个字节表示,首字节是“110”开头,后面的5位与源字符中除去头5个零后的最高5位相同;第二个字节以“10”开头,后面的6位与源字符中的低6位相同。如“\u025d”(0000 0010 0101 1101),转化后为“c99d”(1100 1001 1001 1101);) G4 w2 |% B/ G/ S4 M3 {

' L* L, x! E4 ?# g  (3)如果不符合上述两个规则,则用三个字节表示。第一个字节以“1110”开头,后四位为源字符的高四位;第二个字节以“10”开头,后六位为源字符中间的六位;第三个字节以“10”开头,后六位为源字符的低六位;如“\u9da7”(1001 1101 1010 0111),转化为“e9b6a7”(1110 1001 1011 0110 1010 0111);
& E- G0 a9 r4 c8 M/ c  K* W- Z6 o9 T+ _% _1 P4 o. X% N3 P" Q* F  g
  可以这么描述JAVA程序中Unicode与UTF的关系,虽然不绝对:字符串在内存中运行时,表现为Unicode代码,而当要保存到文件或其它介质中去时,用的是UTF。这个转化过程是由writeUTF和readUTF来完成的。& E0 Z3 w4 e: r9 c5 M0 f- m: S. M

7 V4 y. Q$ q; K1 ?9 N  T  好了,基础性的论述差不多了,下面进入正题。
- ~) b1 g! [  e$ e0 S) F, L+ l" d
5 `* x$ N& ]+ [: B0 p) _/ J3 i  先把这个问题想成是一个黑匣子。先看黑匣子的一级表示:
. v0 D! q8 Q1 m9 K" ^5 e$ l( P( T, C0 f3 q, F# r( V
input(charsetA)->process(Unicode)->output(charsetB)* E8 \* ?' R6 @* d, T' d4 l

) ~' f' S. r, D- |7 l  简单,这就是一个IPO模型,即输入、处理和输出。同样的内容要经过“从charsetA到unicode再到charsetB”的转化。; w7 ]9 t% |$ v  z3 @7 ]
3 _/ [8 V# c* u! L
  再看二级表示:% ~. ]2 G' }5 o1 D

$ D$ k9 H$ K  T# O8 hSourceFile(jsp,java)->class->output
% G! {8 z" |' q' X" n4 P% t7 S( z- _( b* a1 s& \
  在这个图中,可以看出,输入的是jsp和java源文件,在处理过程中,以Class文件为载体,然后输出。再细化到三级表示:
7 I4 \' e' t+ q# U4 e" L6 P1 y7 F& w7 t
jsp->temp file->class->browser,os console,db' @: u6 ]7 c3 s7 t' Q

. F. R2 V7 s* Y7 s. H8 P6 Q2 ~app,servlet->class->browser,os console,db
8 D: s" A- I2 I5 {( s6 [) g) L
# X2 c; q. j7 c& A  这个图就更明白了。Jsp文件先生成中间的Java文件,再生成Class。而Servlet和普通App则直接编译生成Class。然后,从Class再输出到浏览器、控制台或数据库等。
' t  v. v  s& v. D
$ c. A% a  n* a+ Q& D7 y; M: M  JSP:从源文件到Class的过程+ N9 ]/ j- U6 D) p4 W- d
0 `" s9 U3 c. e; |; S) F( J* S
  Jsp的源文件是以“.jsp”结尾的文本文件。在本节中,将阐述JSP文件的解释和编译过程,并跟踪其中的中文变化。
: I) N  k+ ^+ T4 [
1 p+ G) M/ s4 `. F! U  1、JSP/Servlet引擎提供的JSP转换工具(jspc)搜索JSP文件中用<%@ page contentType ="text/html; charset=<Jsp-charset>"%>中指定的charset。如果在JSP文件中未指定<Jsp-charset>,则取JVM中的默认设置file.encoding,一般情况下,这个值是ISO8859-1;
5 f0 \, T- r8 F, Y# b: \% x) I1 [5 U+ F1 q- K- ]: H) R
  2、jspc用相当于“javac –encoding <Jsp-charset>”的命令解释JSP文件中出现的所有字符,包括中文字符和ASCII字符,然后把这些字符转换成Unicode字符,再转化成UTF格式,存为JAVA文件。ASCII码字符转化为Unicode字符时只是简单地在前面加“00”,如“A”,转化为“\u0041”(不需要理由,Unicode的码表就是这么编的)。然后,经过到UTF的转换,又变回“41”了!这也就是可以使用普通文本编辑器查看由JSP生成的JAVA文件的原因;6 s* e; s9 L6 w2 O- X. t+ s/ P

5 a7 T/ R; s/ H6 x  3、引擎用相当于“javac –encoding UNICODE”的命令,把JAVA文件编译成CLASS文件;  w" w" n* [9 u2 \; K/ c2 n- Q
5 c" e1 I; w8 s
  先看一下这些过程中中文字符的转换情况。有如下源代码:6 K& R. g+ r3 I9 h0 ^

1 z+ b7 w# k5 y1 `# T' a! B<%@ page contentType="text/html; charset=gb2312"%>
( W! b  E  n3 h4 B# J. b: ]( i5 x<html><body>: T3 Z7 [' ~. a( M. o
<%$ e" P% @) [+ `3 z% K7 v
 String a="中文";& B6 c, h1 k' a7 V
 out.println(a);
9 r: m. o4 I( S$ F1 ~7 F; ?%>
* b$ I5 S0 h) H  Z# G4 b</body></html>
( ?' c' J; S( R7 z2 [- U  这段代码是在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文件中的完全一样。
/ A; O+ [+ w( A$ r: O$ B7 g5 S! Z! _
( L2 W9 X$ Q1 A( u/ A  再看JSP中指定的CharSet为ISO-8859-1的情况。
* Y* y; h, z0 o7 H7 w2 O8 R
( n9 G2 H9 ?" j* r. K
0 h# k* C5 u( w6 D<%@ page contentType="text/html; charset=ISO-8859-1"%>  O+ |5 h  i, Q" F1 ~, h
<html><body>* k" H: D' n! i, v$ A( g3 F# l
<%
: ~6 P* A1 C! x; R& R7 i  } String a="中文";5 \) U. n" p& L- P, `* s7 _
 out.println(a);
, L/ K4 `2 W2 c$ f%>
' m9 y+ R+ H; x; j</body></html>
/ t9 ^% h7 z9 z+ A- @! U, v* f  同样,该文件是用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”。6 W( H/ ~/ E& Q

/ v, u- z" e8 a  如果上述代码中不指定<Jsp-charset>,即把第一行写成“<%@ page contentType="text/html" %>”,JSPC会使用file.encoding的设置来解释JSP文件。在RedHat 6.2上,其处理结果与指定为ISO-8859-1是完全相同的。
" T4 x1 F" D6 l! I4 Q  e7 T/ o
6 l! k& `( F; V2 R& c& [! s6 A  I  到现在为止,已经解释了从JSP文件到CLASS文件的转变过程中中文字符的映射过程。一句话:从“JspCharSet到Unicode再到UTF”。下表总结了这个过程:
5 @- H8 |8 ]- Q9 b) k, `* Y
$ F+ }1 D2 b3 g8 i7 [  表2 “中文”从JSP到CLASS的转化过程! h3 g$ r/ Y" e3 Z! w- ]
( n" e  J* M& R
, v: z, @- R- C4 d9 M
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
' |5 f6 `. V. U. E+ N8 d4 H* d1 C(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/ d: j- m/ z: M# ?- h
  下节先讨论Servlet从JAVA文件到CLASS文件的转化过程,然后再解释从CLASS文件如何输出到客户端。之所以这样安排,是因为JSP和Servlet在输出时处理方法是一样的。
zan
转播转播0 分享淘帖0 分享分享0 收藏收藏0 支持支持0 反对反对0 微信微信
韩冰        

823

主题

3

听众

4048

积分

我的地盘我做主

该用户从未签到

发帖功臣 元老勋章

Servlet:从源文件到Class的过程
, U" ?" T* S: c4 S" m. H( Z& \0 `% k" A
  Servlet源文件是以“.java”结尾的文本文件。本节将讨论Servlet的编译过程并跟踪其中的中文变化。
8 h6 C0 e" x2 k' w0 k5 K* S- u, s' U
  用“javac”编译Servlet源文件。javac可以带“-encoding <Compile-charset>”参数,意思是“用< Compile-charset >中指定的编码来解释Serlvet源文件”。% l# O* P+ J% t1 L# p
. P. i; I6 }& ^/ s2 B2 L2 L0 X2 d
  源文件在编译时,用<Compile-charset>来解释所有字符,包括中文字符和ASCII字符。然后把字符常量转变成Unicode字符,最后,把Unicode转变成UTF。# N2 L7 V, G4 @# Q7 g; p7 o- h# J
! k5 n) K& E+ `" b2 O$ C! B$ d! b0 \" O; j
  在Servlet中,还有一个地方设置输出流的CharSet。通常在输出结果前,调用HttpServletResponse的setContentType方法来达到与在JSP中设置<Jsp-charset>一样的效果,称之为<Servlet-charset>。
2 {7 E! V2 E/ a2 M% w  W3 Z" Y! l" K8 l3 T# {
  注意,文中一共提到了三个变量:<Jsp-charset>、<Compile-charset>和<Servlet-charset>。其中,JSP文件只与<Jsp-charset>有关,而<Compile-charset>和<Servlet-charset>只与Servlet有关。
, P6 N6 q0 _3 [+ n2 Z" F
) p9 O6 F- u3 ]  看下例:; t: f- B8 X* K& _

! ]" 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>字符集的”。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册地址

qq
收缩
  • 电话咨询

  • 04714969085
fastpost

关于我们| 联系我们| 诚征英才| 对外合作| 产品服务| QQ

手机版|Archiver| |繁體中文 手机客户端  

蒙公网安备 15010502000194号

Powered by Discuz! X2.5   © 2001-2013 数学建模网-数学中国 ( 蒙ICP备14002410号-3 蒙BBS备-0002号 )     论坛法律顾问:王兆丰

GMT+8, 2026-8-17 11:28 , Processed in 0.497653 second(s), 57 queries .

回顶部