QQ登录

只需要一步,快速开始

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

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

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

823

主题

3

听众

4048

积分

我的地盘我做主

该用户从未签到

发帖功臣 元老勋章

跳转到指定楼层
1#
发表于 2004-11-21 12:02 |只看该作者 |倒序浏览
|招呼Ta 关注Ta
世界上的各地区都有本地的语言。地区差异直接导致了语言环境的差异。在开发一个国际化程序的过程中,处理语言问题就显得很重要了。7 _( B- i- B& E& N
1 R  s' y( S8 W6 V
  这是一个世界范围内都存在的问题,所以,Java提供了世界性的解决方法。本文描述的方法是用于处理中文的,但是,推而广之,对于处理世界上其它国家和地区的语言同样适用。# ]# P5 q& r. C% o& W+ x6 T
7 S2 M! f6 J+ x
  汉字是双字节的。所谓双字节是指一个双字要占用两个BYTE的位置(即16位),分别称为高位和低位。中国规定的汉字编码为GB2312,这是强制性的,目前几乎所有的能处理中文的应用程序都支持GB2312。GB2312包括了一二级汉字和9区符号,高位从0xa1到0xfe,低位也是从0xa1到0xfe,其中,汉字的编码范围为0xb0a1到0xf7fe。
) n3 M. O. d/ O* U7 q9 t/ i7 d% Y: c' `; T
  另外有一种编码,叫做GBK,但这是一份规范,不是强制的。GBK提供了20902个汉字,它兼容GB2312,编码范围为0x8140到0xfefe。GBK中的所有字符都可以一一映射到Unicode 2.0。
5 |8 r( v  n9 N; ]8 `. w
% ^( ?; v8 F5 Q& L  在不久的将来,中国会颁布另一种标准:GB18030-2000(GBK2K)。它收录了藏、蒙等少数民族的字型,从根本上解决了字位不足的问题。注意:它不再是定长的。其二字节部份与GBK兼容,四字节部分是扩充的字符、字形。它的首字节和第三字节从0x81到0xfe,二字节和第四字节从0x30到0x39。( N& l$ S2 ?5 l) Z* C7 d3 D& C# P
0 z: b! x" y: I/ }# F
  本文不打算介绍Unicode,有兴趣的可以浏览“http://www.unicode.org/”查看更多的信息。Unicode有一个特性:它包括了世界上所有的字符字形。所以,各个地区的语言都可以建立与Unicode的映射关系,而Java正是利用了这一点以达到异种语言之间的转换。, C9 ^7 a" j; q1 j/ b; v6 V" I

8 h9 Y8 }( l5 ]; B  在JDK中,与中文相关的编码有:
4 h# O" Q0 Q  h# [( y, c: Z' i1 Y! Z
  表1 JDK中与中文相关的编码列表
9 ?( K: c! U1 k0 A7 w$ i
8 e4 ]) j5 h+ B; g% e/ _2 s; Y* ^编码名称说明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很少6 \2 S1 h2 A, p5 d
  在实际编程时,接触得比较多的是GB2312(GBK)和ISO8859-1。7 G2 Y" x$ n! w. E( c( C1 a

, `3 n/ N* V' h0 v5 i& C5 T+ @  为什么会有“?”号
, M5 N- T+ g' @" f  r! a# U: r' t0 E: Y& E+ a4 ?; J
  上文说过,异种语言之间的转换是通过Unicode来完成的。假设有两种不同的语言A和B,转换的步骤为:先把A转化为Unicode,再把Unicode转化为B。+ G" y; l* t% A# A& P: k( ~8 H
$ m& S* u$ v/ r0 h3 r$ ~4 }( K
  举例说明。有GB2312中有一个汉字“李”,其编码为“C0EE”,欲转化为ISO8859-1编码。步骤为:先把“李”字转化为Unicode,得到“674E”,再把“674E”转化为ISO8859-1字符。当然,这个映射不会成功,因为ISO8859-1中根本就没有与“674E”对应的字符。
9 v9 D  B/ g( |3 v" Z& }
$ L$ l' r2 |6 z  @4 T8 y! O; X  当映射不成功时,问题就发生了!当从某语言向Unicode转化时,如果在某语言中没有该字符,得到的将是Unicode的代码“\uffffd”(“\u”表示是Unicode编码,)。而从Unicode向某语言转化时,如果某语言没有对应的字符,则得到的是“0x3f”(“?”)。这就是“?”的由来。
" ^! f6 M1 g6 d; s& E1 F2 ?. \6 @! D! x5 D# ^+ `+ x( A
  例如:把字符流buf =“0x80 0x40 0xb0 0xa1”进行new String(buf, "gb2312")操作,得到的结果是“\ufffd\u554a”,再println出来,得到的结果将是“?啊”,因为“0x80 0x40”是GBK中的字符,在GB2312中没有。' n, p7 r0 c  I5 g, |) i
/ n* F$ H1 x; z2 P! x, _
  再如,把字符串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映射的内容中除了汉字外还有字符,本例就是最好的明证。6 p3 X8 P1 a! _* F# ~) e% Y

7 ]$ n+ d5 \1 s6 K; i  所以,在汉字转码时,如果发生错乱,得到的不一定都是问号噢!不过,错了终究是错了,50步和100步并没有质的差别。
8 E; ^5 a  @' v2 [: X1 K- z4 ]/ g7 h9 v; g3 q
  或者会问:如果源字符集中有,而Unicode中没有,结果会如何?回答是不知道。因为我手头没有能做这个测试的源字符集。但有一点是肯定的,那就是源字符集不够规范。在Java中,如果发生这种情况,是会抛出异常的。9 N0 f8 [0 w9 m" i9 o0 o: W/ p

. P  z$ D0 B- [1 T. Q1 d+ N5 P什么是UTF1 }  S0 n+ c- H3 {2 f- c( o$ v

% F% w2 g) O. ^: A: q* L8 O  UTF,是Unicode Text Format的缩写,意为Unicode文本格式。对于UTF,是这样定义的:$ N# r9 e. g$ K3 k
, _# P& D' z3 G- w! C
  (1)如果Unicode的16位字符的头9位是0,则用一个字节表示,这个字节的首位是“0”,剩下的7位与原字符中的后7位相同,如“\u0034”(0000 0000 0011 0100),用“34” (0011 0100)表示;(与源Unicode字符是相同的);7 w* A& I3 X4 g: `

9 l' d) Z$ V4 J9 i1 \/ B% h1 w9 X  (2)如果Unicode的16位字符的头5位是0,则用2个字节表示,首字节是“110”开头,后面的5位与源字符中除去头5个零后的最高5位相同;第二个字节以“10”开头,后面的6位与源字符中的低6位相同。如“\u025d”(0000 0010 0101 1101),转化后为“c99d”(1100 1001 1001 1101);3 `$ K" O1 r" t0 \% d

4 |) J3 S" ]/ r$ W& ?$ S$ \  n4 `  (3)如果不符合上述两个规则,则用三个字节表示。第一个字节以“1110”开头,后四位为源字符的高四位;第二个字节以“10”开头,后六位为源字符中间的六位;第三个字节以“10”开头,后六位为源字符的低六位;如“\u9da7”(1001 1101 1010 0111),转化为“e9b6a7”(1110 1001 1011 0110 1010 0111);
& k: J, Y2 i" r! a  V0 K% I; N* U  J% `, }. S1 ^0 j
  可以这么描述JAVA程序中Unicode与UTF的关系,虽然不绝对:字符串在内存中运行时,表现为Unicode代码,而当要保存到文件或其它介质中去时,用的是UTF。这个转化过程是由writeUTF和readUTF来完成的。
) v3 ~9 s$ v3 I1 M6 B; ~: _9 o0 X  j. P4 P* W& x0 M0 }) ~
  好了,基础性的论述差不多了,下面进入正题。
$ _6 N2 q! z4 O0 N1 M3 A: U" i3 O1 U7 b; J1 M# \0 j" W4 i
  先把这个问题想成是一个黑匣子。先看黑匣子的一级表示:
8 z, t, Y3 s: ~# `8 |
$ M$ k( ^, y5 l' k  C0 ^8 c* Pinput(charsetA)->process(Unicode)->output(charsetB)
* H' D. ]% r2 t4 a) h# ~, r9 w; R1 [5 a0 _. r2 S
  简单,这就是一个IPO模型,即输入、处理和输出。同样的内容要经过“从charsetA到unicode再到charsetB”的转化。8 }& s0 K% u; t5 A- W* v+ P
2 g- j' h( z0 \$ @" L8 r" v
  再看二级表示:# q: N; m6 L) Z$ M0 S( I8 m9 o" v

& S: M8 |$ y: }4 }SourceFile(jsp,java)->class->output2 V' A) T) O" l" v. A

* L# Q$ d! o3 M; L  m1 W  在这个图中,可以看出,输入的是jsp和java源文件,在处理过程中,以Class文件为载体,然后输出。再细化到三级表示:+ J+ [# D. w( q; L

3 U( k3 V8 d: F% K( K: Wjsp->temp file->class->browser,os console,db
; r* \1 e* F) J) Z, c; u, H
! M, ~# k! B9 [" A& S1 sapp,servlet->class->browser,os console,db
( j( ~( x( {; r8 _# R
% U* I9 W8 _2 e9 I' i  这个图就更明白了。Jsp文件先生成中间的Java文件,再生成Class。而Servlet和普通App则直接编译生成Class。然后,从Class再输出到浏览器、控制台或数据库等。
# J, f; @2 W4 G
' N5 d4 v3 B) h9 H6 J  JSP:从源文件到Class的过程
1 h. q2 R7 v1 V" h% ^) y: E1 i+ a# H2 e$ t- x# f
  Jsp的源文件是以“.jsp”结尾的文本文件。在本节中,将阐述JSP文件的解释和编译过程,并跟踪其中的中文变化。) e4 a+ v) }! z1 s. w7 \. H; C
. p1 ]. ]) [. R- A
  1、JSP/Servlet引擎提供的JSP转换工具(jspc)搜索JSP文件中用<%@ page contentType ="text/html; charset=<Jsp-charset>"%>中指定的charset。如果在JSP文件中未指定<Jsp-charset>,则取JVM中的默认设置file.encoding,一般情况下,这个值是ISO8859-1;$ ?3 g# T/ n2 X2 C- L" e
4 B$ d% u$ I2 m4 B; Z: h, o
  2、jspc用相当于“javac –encoding <Jsp-charset>”的命令解释JSP文件中出现的所有字符,包括中文字符和ASCII字符,然后把这些字符转换成Unicode字符,再转化成UTF格式,存为JAVA文件。ASCII码字符转化为Unicode字符时只是简单地在前面加“00”,如“A”,转化为“\u0041”(不需要理由,Unicode的码表就是这么编的)。然后,经过到UTF的转换,又变回“41”了!这也就是可以使用普通文本编辑器查看由JSP生成的JAVA文件的原因;9 ?: v6 b) b8 E. l" M7 a- b1 ^6 |

* r9 T& M( @, z. e0 ?' ]- ^5 V  3、引擎用相当于“javac –encoding UNICODE”的命令,把JAVA文件编译成CLASS文件;% S5 P: W5 t/ J' g/ k

: I  Q" E% D7 i1 o  先看一下这些过程中中文字符的转换情况。有如下源代码:2 r5 ]7 G" x. q" F
2 V3 F# U/ V, w: O6 J! k3 e5 F! @: \
<%@ page contentType="text/html; charset=gb2312"%>
. A5 A* z0 O+ j, n5 R* ]# D7 ~<html><body>9 [& ]: S- q% r0 I& f2 r+ F9 ~
<%
( H; G0 U# V& t+ j String a="中文";
; F' ^4 P; R+ {7 S1 @7 m out.println(a);
0 O9 F$ @7 ~0 t+ t/ T%>- |$ g3 t( r/ N. Z) A6 O6 Y
</body></html>& t% T" n+ A+ V" j
  这段代码是在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文件中的完全一样。; R4 j; U' A" u" A, B$ S

0 o. |1 q& }0 f0 f2 J3 {  再看JSP中指定的CharSet为ISO-8859-1的情况。
- X) p* g) \: L; q/ g3 Y+ ^# S# t( M  [2 X
! p$ X, \' C; a; q
<%@ page contentType="text/html; charset=ISO-8859-1"%>
. i& ^( k2 y: g& \3 w' S<html><body>
" i$ u. K6 |# B<%
% H2 h# ]( b% `; W String a="中文";- }" K' h) [( i/ G0 Z' m. e  d
 out.println(a);; k! x' S) Y3 l6 S3 B
%>
+ Q; r0 i3 n' Q2 c) j0 Q, T' [</body></html>0 B  A; F3 B/ ^3 _* 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”。# ?0 s1 y* G- P! C3 W/ ^: q* w2 Q
: f" W% P( a6 Z) r8 |
  如果上述代码中不指定<Jsp-charset>,即把第一行写成“<%@ page contentType="text/html" %>”,JSPC会使用file.encoding的设置来解释JSP文件。在RedHat 6.2上,其处理结果与指定为ISO-8859-1是完全相同的。
. L' i  l+ t& o" `6 l& y4 ^! ?7 K* D
  到现在为止,已经解释了从JSP文件到CLASS文件的转变过程中中文字符的映射过程。一句话:从“JspCharSet到Unicode再到UTF”。下表总结了这个过程:
) V" {! \. N' c# D  }
/ w% |2 z8 s( H- J  表2 “中文”从JSP到CLASS的转化过程/ p3 y. n9 S1 @) _* A) T5 G

; p; D4 b0 c2 |( X( e1 ^/ M! a, x" s$ b3 x
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
9 ~- L0 \8 b* S  X) R* d) f6 g3 m) D(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' n/ W  C) \5 G
  下节先讨论Servlet从JAVA文件到CLASS文件的转化过程,然后再解释从CLASS文件如何输出到客户端。之所以这样安排,是因为JSP和Servlet在输出时处理方法是一样的。
zan
转播转播0 分享淘帖0 分享分享0 收藏收藏0 支持支持0 反对反对0 微信微信
韩冰        

823

主题

3

听众

4048

积分

我的地盘我做主

该用户从未签到

发帖功臣 元老勋章

Servlet:从源文件到Class的过程0 X! a8 o* J4 e4 _3 g

7 e0 v; T/ M: f3 C  Servlet源文件是以“.java”结尾的文本文件。本节将讨论Servlet的编译过程并跟踪其中的中文变化。) Q6 {: h- B; P  W7 p: v$ Z
$ J  d" n# j9 j+ w4 v
  用“javac”编译Servlet源文件。javac可以带“-encoding <Compile-charset>”参数,意思是“用< Compile-charset >中指定的编码来解释Serlvet源文件”。
* ?5 B% Z4 O/ ], g; B! A( S5 V
" p, z; K* t" M# [" n) Q  源文件在编译时,用<Compile-charset>来解释所有字符,包括中文字符和ASCII字符。然后把字符常量转变成Unicode字符,最后,把Unicode转变成UTF。
/ V2 k4 O  ^" x, Y3 l4 p" i
. Q5 @0 ?6 W$ q& i7 x/ R6 s  在Servlet中,还有一个地方设置输出流的CharSet。通常在输出结果前,调用HttpServletResponse的setContentType方法来达到与在JSP中设置<Jsp-charset>一样的效果,称之为<Servlet-charset>。
, W$ G3 l) D' u/ D0 L
0 K  g6 K+ t9 R; b' ]- R  注意,文中一共提到了三个变量:<Jsp-charset>、<Compile-charset>和<Servlet-charset>。其中,JSP文件只与<Jsp-charset>有关,而<Compile-charset>和<Servlet-charset>只与Servlet有关。$ F" `/ U  s9 t: z: L9 l- Y) Y
& ]6 H: {+ H$ f: F3 L9 P; Q8 Q
  看下例:
5 }  E5 }* L# @% t$ ?9 s7 U7 K
2 S- H; a5 B2 x7 i, ], @import javax.servlet.*;, [6 N2 L# ~  M. k  T( n( ~
: F/ v" d& ]) K$ v7 _9 K
import javax.servlet.http.*;3 i8 Z4 s0 {# G1 n# G

. R3 n7 ~5 q3 M1 sclass testServlet extends HttpServlet
8 {1 K* s9 z9 ?: V) f* U7 p* q9 N9 e% E+ {{" ]8 k+ \6 h% X5 k2 N: W
 public void doGet(HttpServletRequest req,HttpServletResponse resp)  l! V4 c. G1 T
 throws ServletException,java.io.IOException
9 h9 ]4 z$ X! l {
7 S9 m* B! F( f; V/ Z$ _) }; p  resp.setContentType("text/html; charset=GB2312");
$ n, e, l$ D/ b( f  java.io.PrintWriter out=resp.getWriter();: H* y, O  A0 Z- A
  out.println("<html>");2 n$ q" G$ w5 n
  out.println("#中文#");
9 e5 M2 Y  ^3 A: q8 ?/ j  out.println("</html>");
9 W8 G0 c/ r+ P% w0 u }
  ]& ^7 s: r: p9 o. l}
0 c1 ?3 y% g( T' T4 `  该文件也是用UltraEdit for Windows编写的,其中的“中文”两个字保存为“D6 D0 CE C4”(GB2312编码)。$ q+ O3 e5 j1 [+ L+ B8 F0 a

# S% P0 k8 r5 f: L* K  开始编译。下表是<Compile-charset>不同时,CLASS文件中“中文”两字的十六进制码。在编译过程中,<Servlet-charset>不起任何作用。<Servlet-charset>只对CLASS文件的输出产生影响,实际上是<Servlet-charset>和<Compile-charset>一起,达到与JSP文件中的<Jsp-charset>相同的效果,因为<Jsp-charset>对编译和CLASS文件的输出都会产生影响。
1 F7 J% _8 }: @" J2 G* H1 @3 a+ T; u' t5 ]4 {4 Z& S
  表3 “中文”从Servlet源文件到Class的转变过程1 r- q6 ?; g1 J; F2 L
# P% G: |6 G  O9 l8 F
Compile-charsetServlet源文件中Class文件中等效的Unicode码GB2312D6 D0 CE C4 4 W$ `3 p' Z( x( m
(GB2312)E4 B8 AD E6 96 87 (UTF)\u4E2D\u6587 (在Unicode中=“中文”)ISO-8859-1D6 D0 CE C4 0 @( k" A: r( _7 a
(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
) u* P0 I6 i/ R& ~) q1 @# \* K  普通Java程序的编译过程与Servlet完全一样。
* l+ @* y% `/ ?( Y6 J
. n8 Z, @7 H1 r! g  CLASS文件中的中文表示法是不是昭然若揭了?OK,接下来看看CLASS又是怎样输出中文的呢?* U3 ?9 T; Y, U+ R! n

0 _5 _3 c* x  y4 p/ s8 g  Class:输出字符串
5 a6 @! g4 u" S- U! l3 D
* y6 w, h- `1 _$ ~& A6 ]2 S  上文说过,字符串在内存中表现为Unicode编码。至于这种Unicode编码表示了什么,那要看它是从哪种字符集映射过来的,也就是说要看它的祖先。这好比在托运行李时,外观都是纸箱子,里面装了什么就要看寄邮件的人实际邮了什么东西。: _  o/ h. u" s% q9 V

7 p4 ~7 V* R# S; v5 u' _, ~% U$ s  看看上面的例子,如果给一串Unicode编码“00D6 00D0 00CE 00C4”,如果不作转换,直接用Unicode码表来对照它时,是四个字符(而且是特殊字符);假如把它与“ISO8859-1”进行映射,则直接去掉前面的“00”即可得到“D6 D0 CE C4”,这是ASCII码表中的四个字符;而假如把它当作GB2312来进行映射,得到的结果很可能是一大堆乱码,因为在GB2312中有可能没有(也有可能有)字符与00D6等字符对应(如果对应不上,将得到0x3f,也就是问号,如果对应上了,由于00D6等字符太靠前,估计也是一些特殊符号,真正的汉字在Unicode中的编码从4E00开始)。/ |! G5 g6 V8 v+ I; i
; L$ I, y* r- k$ Y) X" P
  各位看到了,同样的Unicode字符,可以解释成不同的样子。当然,这其中有一种是我们期望的结果。以上例而论,“D6 D0 CE C4”应该是我们所想要的,当把“D6 D0 CE C4”输出到IE中时,用“简体中文”方式查看,就能看到清楚的“中文”两个字了。(当然了,如果你一定要用“西欧字符”来看,那也没办法,你将得不到任何有何时何地的东西)为什么呢?因为“00D6 00D0 00CE 00C4”本来就是由ISO8859-1转化过去的。4 R0 j$ h; H1 q, l$ S+ z4 H8 v, M

2 Q6 L/ ~9 W9 G/ ~( S6 b给出如下结论:
2 b$ q+ S' Y: E" a4 x. p* z6 m7 Y2 |  a& I8 m/ _
  在Class输出字符串前,会将Unicode的字符串按照某一种内码重新生成字节流,然后把字节流输入,相当于进行了一步“String.getBytes(???)”操作。???代表某一种字符集。
) h/ }/ D. L+ p& i) K& }! l' I+ ^  i
, x: T. l& d+ `( Y, N( H  如果是Servlet,那么,这种内码就是在HttpServletResponse.setContentType()方法中指定的内码,也就是上文定义的<Servlet-charset>。
, t4 r% i) ?7 b% A2 W" U) V& ]" Q- V* ?( M8 m' @$ o, V
  如果是JSP,那么,这种内码就是在<%@ page contentType=""%>中指定的内码,也就是上文定义的<Jsp-charset>。
& A3 x# N+ U# r. e5 o; O, R
6 o3 u2 f+ ^, c  如果是Java程序,那么,这种内码就是file.encoding中指定的内码,默认为ISO8859-1。
1 G4 M& P0 M5 d$ H1 U
9 L  o' c) L( G% X/ P- ^( ~6 L  当输出对象是浏览器时
, a3 M7 D$ h' l4 X2 T7 d
- l4 K5 N3 W/ P2 A& @/ B  以流行的浏览器IE为例。IE支持多种内码。假如IE接收到了一个字节流“D6 D0 CE C4”,你可以尝试用各种内码去查看。你会发现用“简体中文”时能得到正确的结果。因为“D6 D0 CE C4”本来就是简体中文中“中文”两个字的编码。
6 x0 C$ q" Q( y6 g, N) t& `* W2 }1 d$ [
  OK,完整地看一遍。
- Q. Z8 S& F, {
/ L0 E& x8 t7 J  JSP:源文件为GB2312格式的文本文件,且JSP源文件中有“中文”这两个汉字
% k8 ^6 P& p) [; n0 C, K
0 B0 Y& O' ^2 E6 j5 X5 X  如果指定了<Jsp-charset>为GB2312,转化过程如下表。
7 J/ F! w  ~1 k" }+ m! k7 d2 {0 J
  表4 Jsp-charset = GB2312时的变化过程
6 h" L4 u4 d' J( @7 B5 m; n& G0 E9 c' C- |
序号步骤说明结果1编写JSP源文件,且存为GB2312格式D6 D0 CE C45 ?' M4 s6 A9 U2 X. `8 o9 e  s& G
(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用“简体中文”查看结果“中文”(正确显示)
) ~7 \% T+ D6 {& E* o! t+ A: y/ d  如果指定了<Jsp-charset>为ISO8859-1,转化过程如下表。
; e2 {* p: W) }5 F; L
8 d; v. m2 P! E7 i0 _  表5 Jsp-charset = ISO8859-1时的变化过程3 k! z1 p# ^' S! A$ \' m( v; k

$ m* R5 c* q( f0 W/ k序号步骤说明结果1编写JSP源文件,且存为GB2312格式D6 D0 CE C4
( |$ U* b0 J/ U1 \(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 C41 {9 K$ f' I. N- @3 R
(啥都不是!!!)5根据Jsp-charset=ISO8859-1把Unicode转化为字节流D6 D0 CE C46把字节流输出到IE中,并设置IE的编码为ISO8859-1(作者按:这个信息隐藏在HTTP头中)D6 D0 CE C47IE用“西欧字符”查看结果乱码,其实是四个ASCII字符,但由于大于128,所以显示出来的怪模怪样8改变IE的页面编码为“简体中文”“中文”(正确显示)$ t2 ^7 L3 t3 ?6 E5 [- J. W
  奇怪了!为什么把<Jsp-charset>设成GB2312和ISO8859-1是一个样的,都能正确显示?因为表4表5中的第2步和第5步互逆,是相互“抵消”的。只不过当指定为ISO8859-1时,要增加第8步操作,殊为不便。# n9 n! b" P: O! e
1 o2 Y, ?' [* o' }4 z
  再看看不指定<Jsp-charset> 时的情况。
! }1 D+ L# s2 B7 V2 Q6 x
$ n% y$ h% d& V2 }2 i  表6 未指定Jsp-charset 时的变化过程
6 Y/ j1 l: Q" c9 H
3 q; s7 h8 z! Q4 E1 Z) A序号步骤说明结果1编写JSP源文件,且存为GB2312格式D6 D0 CE C4
$ n+ H& v/ K0 r  }1 [6 \$ F. @- E(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步
) H2 H! Z. `9 P6 E  Servlet:源文件为JAVA文件,格式是GB2312,源文件中含有“中文”这两个汉字& B7 _: V: K/ R
' C* `# l5 ]  X, _+ b& M
  如果<Compile-charset>=GB2312,<Servlet-charset>=GB2312+ S3 x0 t  _% F: h3 \* F

8 J4 y# d7 C7 O* x  表7 Compile-charset=Servlet-charset=GB2312 时的变化过程5 d+ P7 l, M/ [% w! U: n2 j
( U5 V/ t: z. t9 M: X
序号步骤说明结果1编写Servlet源文件,且存为GB2312格式D6 D0 CE C42 _  s9 M2 D" x9 }
(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用“简体中文”查看结果“中文”(正确显示): o6 `: ~* n' s5 B* W+ G7 Y
  如果<Compile-charset>=ISO8859-1,<Servlet-charset>=ISO8859-1: K# j& F" i6 b! R# f( n
" e$ m$ Z" F& Q8 K; G4 |  H. R2 f
  表8 Compile-charset=Servlet-charset=ISO8859-1时的变化过程
6 n8 c- V; I. n; I: ?/ @$ b: O9 B, A3 m1 x1 Y/ j' h
序号步骤说明结果1编写Servlet源文件,且存为GB2312格式D6 D0 CE C4
1 a6 y  O% s. W! r(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的页面编码为“简体中文”“中文”(正确显示)
4 h2 t/ x. l! d+ _( V1 v  如果不指定Compile-charset或Servlet-charset,其默认值均为ISO8859-1。& V3 A" x) g" [7 N# d/ x" b" H; v
) c; G$ N' ?8 m. u
  当Compile-charset=Servlet-charset时,第2步和第4步能互逆,“抵消”,显示结果均能正确。读者可试着写一下Compile-charset<>Servlet-charset时的情况,肯定是不正确的。
3 a6 E2 t0 F% x
- F0 k6 q* B' a; Q6 _  当输出对象是数据库时- O; G8 _1 s4 p+ A/ p7 w
1 n& T) ~+ ?5 s
  输出到数据库时,原理与输出到浏览器也是一样的。本节只是Servlet为例,JSP的情况请读者自行推导。* m" p1 }3 r/ d9 {! O" C1 y2 ]$ r
4 l* c2 ~5 l- R
  假设有一个Servlet,它能接收来自客户端(IE,简体中文)的汉字字符串,然后把它写入到内码为ISO8859-1的数据库中,然后再从数据库中取出这个字符串,显示到客户端。
6 h4 O% k& Y6 c2 M9 e- r: y, _7 e+ n0 [3 K! P! M* n. T
  表9 输出对象是数据库时的变化过程(1), k6 ~# _( O0 W. G

9 ~1 K0 Q5 J3 W0 r) p# T序号步骤说明结果域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
8 v4 E5 }' M2 ^+ p(Unicode) Servlet准备把字符串输出到客户端16Servlet根据<Servlet-charset>生成字节流D6D0 CE C4Servlet17Servlet把字节流输出到IE中,如果已指定<Servlet-charset>,还会设置IE的编码为<Servlet-charset>D6 D0 CE C418IE根据指定的编码或默认编码查看结果“中文”(正确显示)IE
% H: L$ k6 ~4 [) A" ^: T  解释一下,表中第4第5步和第15第16步是用红色标记的,表示要由编码者来作转换。第4、5两步其实就是一句话:“new String(source.getBytes("GB2312"), "ISO8859-1")”。第15、16两步也是一句话:“new String(source.getBytes("ISO8859-1"), "GB2312")”。亲爱的读者,你在这样编写代码时是否意识到了其中的每一个细节呢?
6 b: ~* l8 [0 X/ {1 b, f* \  m, R  n3 H% Q* R
  至于客户端内码和数据库内码为其它值时的流程,和输出对象是系统控制台时的流程,请读者自己想吧。明白了上述流程的原理,相信你可以轻松地写出来。' g* T% F. D& K: g

, k# T4 L" g$ j2 t# M  行文至此,已可告一段落了。终点又回到了起点,对于编程者而言,几乎是什么影响都没有。
( u. [2 f. C  n/ i  k4 u. v
% s3 ]1 O& v+ K$ F7 |  因为我们早就被告之要这么做了。
- J" c5 Q3 M7 _$ X+ }+ C; ]3 r6 a: ~; y5 O& ~4 y% o
  以下给出一个结论,作为结尾。1 W$ Q! M1 G/ B$ N, V
/ c6 ~3 `+ m0 v  f1 c
  1、 在Jsp文件中,要指定contentType,其中,charset的值要与客户端浏览器所用的字符集一样;对于其中的字符串常量,不需做任何内码转换;对于字符串变量,要求能根据ContentType中指定的字符集还原成客户端能识别的字节流,简单地说,就是“字符串变量是基于<Jsp-charset>字符集的”;1 E+ w7 Y/ O, J- d) W0 Q

/ d: G/ t. D- c. V" ^; X+ N  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 13:15 , Processed in 0.494466 second(s), 56 queries .

回顶部