QQ登录

只需要一步,快速开始

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

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

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

823

主题

3

听众

4048

积分

我的地盘我做主

该用户从未签到

发帖功臣 元老勋章

跳转到指定楼层
1#
发表于 2004-11-21 12:02 |只看该作者 |正序浏览
|招呼Ta 关注Ta
世界上的各地区都有本地的语言。地区差异直接导致了语言环境的差异。在开发一个国际化程序的过程中,处理语言问题就显得很重要了。
, ^* S8 w) m+ T# W) j, w7 D3 f+ X$ J1 j+ v& o! U' w: [7 T( V. N
  这是一个世界范围内都存在的问题,所以,Java提供了世界性的解决方法。本文描述的方法是用于处理中文的,但是,推而广之,对于处理世界上其它国家和地区的语言同样适用。
5 y* @. Z& Q; C$ E) i) @+ r6 y' s
  汉字是双字节的。所谓双字节是指一个双字要占用两个BYTE的位置(即16位),分别称为高位和低位。中国规定的汉字编码为GB2312,这是强制性的,目前几乎所有的能处理中文的应用程序都支持GB2312。GB2312包括了一二级汉字和9区符号,高位从0xa1到0xfe,低位也是从0xa1到0xfe,其中,汉字的编码范围为0xb0a1到0xf7fe。
2 \5 O0 j' `% A2 ~2 |" n- P4 m# }, t. G& l
  另外有一种编码,叫做GBK,但这是一份规范,不是强制的。GBK提供了20902个汉字,它兼容GB2312,编码范围为0x8140到0xfefe。GBK中的所有字符都可以一一映射到Unicode 2.0。
* T* E# G3 Q# |( n
% h  u5 A5 ?, B8 R' H  在不久的将来,中国会颁布另一种标准:GB18030-2000(GBK2K)。它收录了藏、蒙等少数民族的字型,从根本上解决了字位不足的问题。注意:它不再是定长的。其二字节部份与GBK兼容,四字节部分是扩充的字符、字形。它的首字节和第三字节从0x81到0xfe,二字节和第四字节从0x30到0x39。: h( ^, B5 ]& H$ N  w4 L) Q! V

# B, H, d% Q  L& Q) z  本文不打算介绍Unicode,有兴趣的可以浏览“http://www.unicode.org/”查看更多的信息。Unicode有一个特性:它包括了世界上所有的字符字形。所以,各个地区的语言都可以建立与Unicode的映射关系,而Java正是利用了这一点以达到异种语言之间的转换。- u' ]2 X- q4 L5 ^& ^, H
! ~$ z9 Y$ w* a0 O. {5 G
  在JDK中,与中文相关的编码有:
$ u! i9 L, B1 h& A  i% {* q( s, R6 T3 [- i4 @8 g& a: U
  表1 JDK中与中文相关的编码列表. Z0 V. l. f! Z/ C* w' c

$ _2 H  q, {* e+ {, _* C3 ^3 @/ h, h% o编码名称说明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很少
3 {" P9 q; ~9 l7 a* J' f& s. Q6 d  在实际编程时,接触得比较多的是GB2312(GBK)和ISO8859-1。5 N3 O) \/ W% N) O

* N& d2 z* G+ B1 h  为什么会有“?”号3 H% j0 v- F# Z' h+ I

% f3 K, e& k) k5 a) x/ M' V* m  上文说过,异种语言之间的转换是通过Unicode来完成的。假设有两种不同的语言A和B,转换的步骤为:先把A转化为Unicode,再把Unicode转化为B。
; G% z+ C: i+ J; v/ f1 x7 E9 I, V  b
  举例说明。有GB2312中有一个汉字“李”,其编码为“C0EE”,欲转化为ISO8859-1编码。步骤为:先把“李”字转化为Unicode,得到“674E”,再把“674E”转化为ISO8859-1字符。当然,这个映射不会成功,因为ISO8859-1中根本就没有与“674E”对应的字符。4 i) G! y' C/ h$ t9 s0 D  g

% T/ B( t3 H( x+ n. h6 U  当映射不成功时,问题就发生了!当从某语言向Unicode转化时,如果在某语言中没有该字符,得到的将是Unicode的代码“\uffffd”(“\u”表示是Unicode编码,)。而从Unicode向某语言转化时,如果某语言没有对应的字符,则得到的是“0x3f”(“?”)。这就是“?”的由来。" z( }+ u8 d4 u! o: r# R1 Q8 S& u
7 Z- }' w7 t1 g
  例如:把字符流buf =“0x80 0x40 0xb0 0xa1”进行new String(buf, "gb2312")操作,得到的结果是“\ufffd\u554a”,再println出来,得到的结果将是“?啊”,因为“0x80 0x40”是GBK中的字符,在GB2312中没有。0 w& U- a6 |# s3 J; c/ j
! o  C7 F" l! O( d
  再如,把字符串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映射的内容中除了汉字外还有字符,本例就是最好的明证。
0 P- ?* B( K2 o% W
% \9 A/ Y; |3 w" n( H7 L2 n- J) X+ D  所以,在汉字转码时,如果发生错乱,得到的不一定都是问号噢!不过,错了终究是错了,50步和100步并没有质的差别。; k. {1 W/ l5 Q0 W. x
$ L8 n% q7 O& T% k' Q
  或者会问:如果源字符集中有,而Unicode中没有,结果会如何?回答是不知道。因为我手头没有能做这个测试的源字符集。但有一点是肯定的,那就是源字符集不够规范。在Java中,如果发生这种情况,是会抛出异常的。
3 F4 N+ ?* y& A3 c& ?9 S2 j0 [* {7 O8 }. {& V
什么是UTF
1 G9 w' `8 c' Z0 H! k9 ?5 ^2 B) o) L% d1 G$ |- Q! t
  UTF,是Unicode Text Format的缩写,意为Unicode文本格式。对于UTF,是这样定义的:5 o4 ?0 W# l5 a5 _
" E( Q( d9 _6 f8 S# m! v  p7 a
  (1)如果Unicode的16位字符的头9位是0,则用一个字节表示,这个字节的首位是“0”,剩下的7位与原字符中的后7位相同,如“\u0034”(0000 0000 0011 0100),用“34” (0011 0100)表示;(与源Unicode字符是相同的);% B* i4 `5 u7 K  g; \# E0 M2 t
7 F' y" [2 k0 v  M7 W8 p
  (2)如果Unicode的16位字符的头5位是0,则用2个字节表示,首字节是“110”开头,后面的5位与源字符中除去头5个零后的最高5位相同;第二个字节以“10”开头,后面的6位与源字符中的低6位相同。如“\u025d”(0000 0010 0101 1101),转化后为“c99d”(1100 1001 1001 1101);
# n* }: [. ~2 {) l
, m. u2 a& B4 `1 b" B  (3)如果不符合上述两个规则,则用三个字节表示。第一个字节以“1110”开头,后四位为源字符的高四位;第二个字节以“10”开头,后六位为源字符中间的六位;第三个字节以“10”开头,后六位为源字符的低六位;如“\u9da7”(1001 1101 1010 0111),转化为“e9b6a7”(1110 1001 1011 0110 1010 0111);
) t% z& E: \6 F- h) e/ L, D  x% w' b/ W, X% M- X
  可以这么描述JAVA程序中Unicode与UTF的关系,虽然不绝对:字符串在内存中运行时,表现为Unicode代码,而当要保存到文件或其它介质中去时,用的是UTF。这个转化过程是由writeUTF和readUTF来完成的。% p" Q* e- D" O

* `3 w9 P& ?: k7 @3 r- k  好了,基础性的论述差不多了,下面进入正题。
" d6 W6 s, V. `2 P+ l+ |8 O2 A( g2 B  M% w8 H* m3 w
  先把这个问题想成是一个黑匣子。先看黑匣子的一级表示:
  @$ {, f) J- c( @1 u9 t' w9 k/ z4 N$ g/ C! ?* U& a2 o
input(charsetA)->process(Unicode)->output(charsetB)+ `; x& A4 d+ U% G; p4 K9 [- t

. `  Z& L  L$ j4 ^  简单,这就是一个IPO模型,即输入、处理和输出。同样的内容要经过“从charsetA到unicode再到charsetB”的转化。
7 h/ W6 D, {) f2 ~# q! P1 l5 O1 n0 N, Y, w
  再看二级表示:
4 G- m7 X2 A* o% D8 N/ G7 E$ A1 K* q" z% b: t% N
SourceFile(jsp,java)->class->output' C) R+ s, R- ^% G7 G( C0 n& e

9 _* Q! k2 }8 \  s! s  在这个图中,可以看出,输入的是jsp和java源文件,在处理过程中,以Class文件为载体,然后输出。再细化到三级表示:
- ?9 R9 I9 x+ C; V1 Y: D$ V2 D  K8 F9 b
jsp->temp file->class->browser,os console,db! G# m- V7 \& q8 h) V

( L2 f- v0 q2 K& _' Z. Wapp,servlet->class->browser,os console,db
+ Z( ~( D6 I/ l* f
2 h5 ^$ I" E2 n/ K3 ~8 K  这个图就更明白了。Jsp文件先生成中间的Java文件,再生成Class。而Servlet和普通App则直接编译生成Class。然后,从Class再输出到浏览器、控制台或数据库等。; F* M$ Z  c( Q% F; E
- y( _4 p- G; i/ Y" |+ F
  JSP:从源文件到Class的过程+ `" |1 [2 F0 d) {# j
+ T9 m5 `5 S- i  Y* W1 @$ w
  Jsp的源文件是以“.jsp”结尾的文本文件。在本节中,将阐述JSP文件的解释和编译过程,并跟踪其中的中文变化。" }* i/ t4 Q# ]$ e$ K8 M

# ^1 F. a# A8 T  l0 \4 e5 u  1、JSP/Servlet引擎提供的JSP转换工具(jspc)搜索JSP文件中用<%@ page contentType ="text/html; charset=<Jsp-charset>"%>中指定的charset。如果在JSP文件中未指定<Jsp-charset>,则取JVM中的默认设置file.encoding,一般情况下,这个值是ISO8859-1;
& {, f& Z) _0 D1 @6 Q
; l7 e% p* P7 S" @  2、jspc用相当于“javac –encoding <Jsp-charset>”的命令解释JSP文件中出现的所有字符,包括中文字符和ASCII字符,然后把这些字符转换成Unicode字符,再转化成UTF格式,存为JAVA文件。ASCII码字符转化为Unicode字符时只是简单地在前面加“00”,如“A”,转化为“\u0041”(不需要理由,Unicode的码表就是这么编的)。然后,经过到UTF的转换,又变回“41”了!这也就是可以使用普通文本编辑器查看由JSP生成的JAVA文件的原因;8 f7 L- X2 p9 Q- ]
$ l5 a! @; J  h2 e
  3、引擎用相当于“javac –encoding UNICODE”的命令,把JAVA文件编译成CLASS文件;# P+ A! b; B, e; A7 B/ d

1 z* [2 a+ u. g6 L1 }  W  先看一下这些过程中中文字符的转换情况。有如下源代码:
4 n( e, h$ s. ^8 I
1 i6 J$ w* l- i6 B* X9 |<%@ page contentType="text/html; charset=gb2312"%>
& ?3 [, k/ B% u+ m  J8 n<html><body>
: \7 O& w5 x/ U: a( V<%2 Y2 V. ^" w1 i4 k* M
 String a="中文";
) M  r. b6 A+ e4 Y2 V out.println(a);
1 l/ {+ p# |( X& T+ D%>
) m* y5 o, \6 B3 |& Z# b6 f</body></html>; w: \1 e# k8 j" q' M- d1 d
  这段代码是在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文件中的完全一样。
5 r$ g' b) F! o2 W7 |6 s  Q- S& O" R, m8 |& J2 d# @
  再看JSP中指定的CharSet为ISO-8859-1的情况。
0 \* R7 i' q! @9 e, S/ z& C$ z
# U/ n* }% A; z5 I) `, U2 j3 U( B4 x4 H
<%@ page contentType="text/html; charset=ISO-8859-1"%>4 @4 R4 G# g; S$ ]
<html><body>
3 m4 ~3 G# J9 r# d% I8 n<%
  D5 m8 ~# s) l  B' I String a="中文";+ |9 l% ^  T3 t4 `# k6 C* f' W' f  U
 out.println(a);0 h# @0 C6 o! H+ H. N+ r
%>
8 o+ u) ]( C$ f* m& X- ]</body></html>
  m& H+ a# @/ |1 \% L  同样,该文件是用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”。8 J" H2 @) |: E/ G& h: u5 M1 q
: i* Z' S  q4 e' s: c
  如果上述代码中不指定<Jsp-charset>,即把第一行写成“<%@ page contentType="text/html" %>”,JSPC会使用file.encoding的设置来解释JSP文件。在RedHat 6.2上,其处理结果与指定为ISO-8859-1是完全相同的。& o4 H2 L) }7 t- u: U2 C: K; O# {. I
) `! k" a8 ^# Q. B! S# w, ^  o
  到现在为止,已经解释了从JSP文件到CLASS文件的转变过程中中文字符的映射过程。一句话:从“JspCharSet到Unicode再到UTF”。下表总结了这个过程:# r$ @1 O8 D; i, ^

1 z/ c% t4 m/ I  表2 “中文”从JSP到CLASS的转化过程$ t* I- a3 M* o  h  U+ U8 \
! c* Y. f2 z% @( g
1 ?: i% ]5 Z# B' P: j
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% f; C! a/ j1 e+ M; l! p
(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* o5 `+ ^% n' L. Y0 u# {* d, o
  下节先讨论Servlet从JAVA文件到CLASS文件的转化过程,然后再解释从CLASS文件如何输出到客户端。之所以这样安排,是因为JSP和Servlet在输出时处理方法是一样的。
zan
转播转播0 分享淘帖0 分享分享0 收藏收藏0 支持支持0 反对反对0 微信微信
韩冰        

823

主题

3

听众

4048

积分

我的地盘我做主

该用户从未签到

发帖功臣 元老勋章

Servlet:从源文件到Class的过程6 q$ P! E* m* L7 D6 i2 `4 h) o5 j
1 s! y7 n$ ^- c, e' ~' v- k
  Servlet源文件是以“.java”结尾的文本文件。本节将讨论Servlet的编译过程并跟踪其中的中文变化。, H& G0 D. N5 t$ }. `  ^6 o
' j7 ?% W) l6 ^, n6 B
  用“javac”编译Servlet源文件。javac可以带“-encoding <Compile-charset>”参数,意思是“用< Compile-charset >中指定的编码来解释Serlvet源文件”。; x7 n  V' G/ t3 Y7 L  R6 J

% ^- n, l( ]& i1 W$ [5 N  源文件在编译时,用<Compile-charset>来解释所有字符,包括中文字符和ASCII字符。然后把字符常量转变成Unicode字符,最后,把Unicode转变成UTF。
5 a- |' l  D4 Z0 }: C7 Y* e, J
# ?4 i3 h& _/ H& M) p  在Servlet中,还有一个地方设置输出流的CharSet。通常在输出结果前,调用HttpServletResponse的setContentType方法来达到与在JSP中设置<Jsp-charset>一样的效果,称之为<Servlet-charset>。
0 k" |0 {3 ]& @$ `4 {) A9 q8 b2 r( r9 \! L# c
  注意,文中一共提到了三个变量:<Jsp-charset>、<Compile-charset>和<Servlet-charset>。其中,JSP文件只与<Jsp-charset>有关,而<Compile-charset>和<Servlet-charset>只与Servlet有关。
$ _7 B8 b4 j4 |. V) u; X: p3 R' S6 f( o
  看下例:
) _/ {( ^( [7 W, a) E7 H8 E6 S
) M3 ?3 H2 \9 p  A* e  `# p! mimport javax.servlet.*;
$ Z0 F; z1 C- [" g& Q, u9 k
4 f6 x% a( @6 B6 V) n9 Zimport javax.servlet.http.*;
+ _- X5 p: ^5 t. J: B+ N6 J: h3 T- N: n7 _' P
class testServlet extends HttpServlet$ O) b8 p, N3 a, e; b2 o
{+ Z$ f) ]( ^9 \9 k& ?! K
 public void doGet(HttpServletRequest req,HttpServletResponse resp)
/ d3 n& ?! @/ L. p throws ServletException,java.io.IOException
8 `2 `  p0 a2 V& E# Q {
' L: j9 R5 s7 y  m; Q+ K( Q# ?  resp.setContentType("text/html; charset=GB2312");# a8 x' K+ d1 P
  java.io.PrintWriter out=resp.getWriter();' D0 c( O& h2 g7 z; y9 i
  out.println("<html>");0 T' Y3 B% o5 f
  out.println("#中文#");; t, W! v( `1 T+ W! N
  out.println("</html>");) J  T" D1 ~( o# U
 }0 D+ d7 s/ M6 {+ B5 {% K1 F
}4 n& w! a! i( y2 g4 S* R( V6 Q
  该文件也是用UltraEdit for Windows编写的,其中的“中文”两个字保存为“D6 D0 CE C4”(GB2312编码)。
3 A  f. x' ~+ O1 |2 X; a$ ^7 r5 ]1 {" f1 c
  开始编译。下表是<Compile-charset>不同时,CLASS文件中“中文”两字的十六进制码。在编译过程中,<Servlet-charset>不起任何作用。<Servlet-charset>只对CLASS文件的输出产生影响,实际上是<Servlet-charset>和<Compile-charset>一起,达到与JSP文件中的<Jsp-charset>相同的效果,因为<Jsp-charset>对编译和CLASS文件的输出都会产生影响。
3 l3 `3 D0 n3 r, Y) ~+ ?1 j. O3 x, R- T/ ?. V2 l
  表3 “中文”从Servlet源文件到Class的转变过程  V- m/ G# @1 g, }% P% k

$ }. [5 W' T0 g" @+ w& S$ RCompile-charsetServlet源文件中Class文件中等效的Unicode码GB2312D6 D0 CE C4 , D# Q1 T( F& e3 _2 w! g0 d$ h
(GB2312)E4 B8 AD E6 96 87 (UTF)\u4E2D\u6587 (在Unicode中=“中文”)ISO-8859-1D6 D0 CE C4
( @9 Z& O) D6 K# I/ W! k8 c, J(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-1: }' g6 b. S# Z! [0 Q9 A
  普通Java程序的编译过程与Servlet完全一样。
9 o, P0 n/ D, `, t# y+ S0 c! ]( Y3 {+ w
  CLASS文件中的中文表示法是不是昭然若揭了?OK,接下来看看CLASS又是怎样输出中文的呢?
6 _4 U* w( r. P
1 u! r! L8 h& v% m' t  Class:输出字符串- _- @3 k* g8 x6 u4 V

9 o9 P  B. _+ t3 l9 V* u  上文说过,字符串在内存中表现为Unicode编码。至于这种Unicode编码表示了什么,那要看它是从哪种字符集映射过来的,也就是说要看它的祖先。这好比在托运行李时,外观都是纸箱子,里面装了什么就要看寄邮件的人实际邮了什么东西。
9 E6 J# H( {) i3 J3 p$ s4 z
! g. x. s6 \) F; y! w1 t1 i. {8 n% ~  看看上面的例子,如果给一串Unicode编码“00D6 00D0 00CE 00C4”,如果不作转换,直接用Unicode码表来对照它时,是四个字符(而且是特殊字符);假如把它与“ISO8859-1”进行映射,则直接去掉前面的“00”即可得到“D6 D0 CE C4”,这是ASCII码表中的四个字符;而假如把它当作GB2312来进行映射,得到的结果很可能是一大堆乱码,因为在GB2312中有可能没有(也有可能有)字符与00D6等字符对应(如果对应不上,将得到0x3f,也就是问号,如果对应上了,由于00D6等字符太靠前,估计也是一些特殊符号,真正的汉字在Unicode中的编码从4E00开始)。
2 {# \" C$ _2 ?# D
! Q# B! V8 v' t: ]9 d  各位看到了,同样的Unicode字符,可以解释成不同的样子。当然,这其中有一种是我们期望的结果。以上例而论,“D6 D0 CE C4”应该是我们所想要的,当把“D6 D0 CE C4”输出到IE中时,用“简体中文”方式查看,就能看到清楚的“中文”两个字了。(当然了,如果你一定要用“西欧字符”来看,那也没办法,你将得不到任何有何时何地的东西)为什么呢?因为“00D6 00D0 00CE 00C4”本来就是由ISO8859-1转化过去的。: k/ g: e7 s; e+ y! m; L' {
8 C* L% T- E! t7 ~. k
给出如下结论:) V8 C  _3 `' e$ r8 o2 D

  w  o+ n! e8 z& I8 f4 j6 R  在Class输出字符串前,会将Unicode的字符串按照某一种内码重新生成字节流,然后把字节流输入,相当于进行了一步“String.getBytes(???)”操作。???代表某一种字符集。! R: s- ?* i, [4 g% {" L1 c
. r9 T9 p4 T4 G/ R, }/ s5 P$ {, W
  如果是Servlet,那么,这种内码就是在HttpServletResponse.setContentType()方法中指定的内码,也就是上文定义的<Servlet-charset>。
, S2 t# S0 r2 R+ Q+ s% g5 [5 X7 j- I7 e/ k0 [% X; u9 Z+ c
  如果是JSP,那么,这种内码就是在<%@ page contentType=""%>中指定的内码,也就是上文定义的<Jsp-charset>。2 {7 H5 Q! l% ^+ f8 n! _- U
3 W; D7 s1 ^" E# f
  如果是Java程序,那么,这种内码就是file.encoding中指定的内码,默认为ISO8859-1。
% x, ^/ E# X" S9 S, i0 L0 y- m% y% `( e) u
  当输出对象是浏览器时
5 k3 X# J+ L# ]; [: H
6 j5 v, V2 z0 [; E! D+ t; I  以流行的浏览器IE为例。IE支持多种内码。假如IE接收到了一个字节流“D6 D0 CE C4”,你可以尝试用各种内码去查看。你会发现用“简体中文”时能得到正确的结果。因为“D6 D0 CE C4”本来就是简体中文中“中文”两个字的编码。4 [$ J7 }2 x, {  P
, M, ~- E0 l8 H' Z  @+ T
  OK,完整地看一遍。
7 z7 P2 I7 U: w6 K$ P5 Z! _4 {. V0 L2 Y7 A
  JSP:源文件为GB2312格式的文本文件,且JSP源文件中有“中文”这两个汉字
" Q5 c5 o" o; _1 Y, d7 S/ D, m8 A, i
  如果指定了<Jsp-charset>为GB2312,转化过程如下表。
  i0 [2 ^! k# g6 A2 U. p  P. y/ m2 m* `4 K' X  k9 `+ Y( ?
  表4 Jsp-charset = GB2312时的变化过程
/ _; Y4 C' \$ T: \
; P6 K# x: ~, L0 d$ z序号步骤说明结果1编写JSP源文件,且存为GB2312格式D6 D0 CE C43 L" r2 i$ Y6 n1 q2 H
(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用“简体中文”查看结果“中文”(正确显示)
, _% b! G1 O4 I( w# r  如果指定了<Jsp-charset>为ISO8859-1,转化过程如下表。: L  X" O% @- ]3 B* [/ U
4 `3 i+ {1 Y6 g4 A1 p1 a
  表5 Jsp-charset = ISO8859-1时的变化过程
/ f+ Q* D( @. Q7 @5 ]$ W) s$ G- U- }
0 K" g" ]- G1 p序号步骤说明结果1编写JSP源文件,且存为GB2312格式D6 D0 CE C4. x; H/ [" V; y
(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
% y2 e: ]: m% D: t  _- ?; E(啥都不是!!!)5根据Jsp-charset=ISO8859-1把Unicode转化为字节流D6 D0 CE C46把字节流输出到IE中,并设置IE的编码为ISO8859-1(作者按:这个信息隐藏在HTTP头中)D6 D0 CE C47IE用“西欧字符”查看结果乱码,其实是四个ASCII字符,但由于大于128,所以显示出来的怪模怪样8改变IE的页面编码为“简体中文”“中文”(正确显示)
! j$ `  ^  ?0 J5 W5 y. Z' G  奇怪了!为什么把<Jsp-charset>设成GB2312和ISO8859-1是一个样的,都能正确显示?因为表4表5中的第2步和第5步互逆,是相互“抵消”的。只不过当指定为ISO8859-1时,要增加第8步操作,殊为不便。* Y2 P2 J  |+ W
2 ?0 d2 |9 e* k* R
  再看看不指定<Jsp-charset> 时的情况。
( m( W+ u- {; I9 k+ {
& i+ G! k* C% U  表6 未指定Jsp-charset 时的变化过程7 q0 R2 _  y" j- t' V8 G9 ]

) g3 C* I# g$ v* J* q序号步骤说明结果1编写JSP源文件,且存为GB2312格式D6 D0 CE C4
8 A9 @: r7 a, G, y(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步) l2 u: F* X$ X* Z# V  |
  Servlet:源文件为JAVA文件,格式是GB2312,源文件中含有“中文”这两个汉字: {' a; O7 E9 I3 C9 M0 H; g6 z
9 R0 y  K9 ?% U3 L+ N  p* r' I
  如果<Compile-charset>=GB2312,<Servlet-charset>=GB2312& g- o6 v5 Y5 g' N4 j
6 l$ L7 H- s' X% i6 [0 M9 B6 l$ t
  表7 Compile-charset=Servlet-charset=GB2312 时的变化过程
! I9 O" V" H3 ]. F
* q  z/ Z! |" O4 Q8 m7 n& c5 X/ y序号步骤说明结果1编写Servlet源文件,且存为GB2312格式D6 D0 CE C4; M8 E+ K+ y. @, R9 Y) M
(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用“简体中文”查看结果“中文”(正确显示)
4 @8 J2 x1 M% H! }" j$ m" o  如果<Compile-charset>=ISO8859-1,<Servlet-charset>=ISO8859-1
$ [; \0 j. ~( N% K' d$ `0 g* x
: m& X( Q& f% m5 N  表8 Compile-charset=Servlet-charset=ISO8859-1时的变化过程9 U$ p2 j: Y' L, b8 t

: C9 C/ m5 W, k. D( ^序号步骤说明结果1编写Servlet源文件,且存为GB2312格式D6 D0 CE C4# f) y' Y8 {5 X$ U7 A
(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的页面编码为“简体中文”“中文”(正确显示)2 a5 `' d% }7 W2 A0 G
  如果不指定Compile-charset或Servlet-charset,其默认值均为ISO8859-1。
7 F# u  U" |7 A. e
/ ^4 D4 q# _8 X: M% e3 {  当Compile-charset=Servlet-charset时,第2步和第4步能互逆,“抵消”,显示结果均能正确。读者可试着写一下Compile-charset<>Servlet-charset时的情况,肯定是不正确的。2 c. G- m  ]7 P* ^: Y
' s1 C- M7 a+ f1 l8 Z* k. U& _& W
  当输出对象是数据库时# [6 H- G' Z- G

% I) L! z3 |5 s4 n+ b3 m( L: q  输出到数据库时,原理与输出到浏览器也是一样的。本节只是Servlet为例,JSP的情况请读者自行推导。
- j0 T! Z" p" l% l. `' |! o5 \% n$ a) J4 Y
  假设有一个Servlet,它能接收来自客户端(IE,简体中文)的汉字字符串,然后把它写入到内码为ISO8859-1的数据库中,然后再从数据库中取出这个字符串,显示到客户端。7 a' q' S' o3 [' Y; d: _; D7 w2 |5 C0 r0 q

9 L# O# O) l9 U0 v$ G5 J/ `  ^. Q  表9 输出对象是数据库时的变化过程(1)7 A( d, g3 y- ^, i5 r
9 a1 R8 y- I" Y8 p3 g+ n" G1 @6 h
序号步骤说明结果域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
7 ?$ D2 E& b) ?; w* p* ^9 y(Unicode) Servlet准备把字符串输出到客户端16Servlet根据<Servlet-charset>生成字节流D6D0 CE C4Servlet17Servlet把字节流输出到IE中,如果已指定<Servlet-charset>,还会设置IE的编码为<Servlet-charset>D6 D0 CE C418IE根据指定的编码或默认编码查看结果“中文”(正确显示)IE
; Q0 a5 D$ ~1 A3 d1 [  x7 J  解释一下,表中第4第5步和第15第16步是用红色标记的,表示要由编码者来作转换。第4、5两步其实就是一句话:“new String(source.getBytes("GB2312"), "ISO8859-1")”。第15、16两步也是一句话:“new String(source.getBytes("ISO8859-1"), "GB2312")”。亲爱的读者,你在这样编写代码时是否意识到了其中的每一个细节呢?4 [8 L4 X( N1 H9 t) J% \
$ T6 k2 u3 n' c5 }, k; y* P8 K
  至于客户端内码和数据库内码为其它值时的流程,和输出对象是系统控制台时的流程,请读者自己想吧。明白了上述流程的原理,相信你可以轻松地写出来。
0 b' u$ D$ b- c: d7 q' F$ h5 ]  b, G
; M8 N7 r7 U. U& B! U8 O: a  行文至此,已可告一段落了。终点又回到了起点,对于编程者而言,几乎是什么影响都没有。
) E( P( l2 e+ U: J; x  {7 S. |8 \( f( R8 H4 A: L
  因为我们早就被告之要这么做了。7 Z/ a3 ^3 H% _9 A9 ?

, m; J. `) Q* W* ^2 V" O% z  以下给出一个结论,作为结尾。
4 v& L6 {" x+ T9 k
* o, U4 B) L3 v$ \  1、 在Jsp文件中,要指定contentType,其中,charset的值要与客户端浏览器所用的字符集一样;对于其中的字符串常量,不需做任何内码转换;对于字符串变量,要求能根据ContentType中指定的字符集还原成客户端能识别的字节流,简单地说,就是“字符串变量是基于<Jsp-charset>字符集的”;  U* \5 i& l+ R" u- U9 `
: H! p* K7 a1 k* U; T/ n  D1 O
  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-4-20 04:23 , Processed in 0.446756 second(s), 57 queries .

回顶部