QQ登录

只需要一步,快速开始

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

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

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

823

主题

3

听众

4048

积分

我的地盘我做主

该用户从未签到

发帖功臣 元老勋章

跳转到指定楼层
1#
发表于 2004-11-21 12:02 |只看该作者 |倒序浏览
|招呼Ta 关注Ta
世界上的各地区都有本地的语言。地区差异直接导致了语言环境的差异。在开发一个国际化程序的过程中,处理语言问题就显得很重要了。2 U7 }) G$ I; Z* }6 O+ i

; U2 W$ l4 a% @- e0 m2 b) @  这是一个世界范围内都存在的问题,所以,Java提供了世界性的解决方法。本文描述的方法是用于处理中文的,但是,推而广之,对于处理世界上其它国家和地区的语言同样适用。! K: E, f8 z8 N' a6 T' R9 B

( N: o) T' v& A: j9 }5 j) L  汉字是双字节的。所谓双字节是指一个双字要占用两个BYTE的位置(即16位),分别称为高位和低位。中国规定的汉字编码为GB2312,这是强制性的,目前几乎所有的能处理中文的应用程序都支持GB2312。GB2312包括了一二级汉字和9区符号,高位从0xa1到0xfe,低位也是从0xa1到0xfe,其中,汉字的编码范围为0xb0a1到0xf7fe。9 f* Y& b. d4 @- \  t
) C+ j, y: u7 S. h, ^, }
  另外有一种编码,叫做GBK,但这是一份规范,不是强制的。GBK提供了20902个汉字,它兼容GB2312,编码范围为0x8140到0xfefe。GBK中的所有字符都可以一一映射到Unicode 2.0。
! z% [- G0 g' G/ p! m8 b3 k+ ^
  g- o/ F) A4 Q  J9 ~  在不久的将来,中国会颁布另一种标准:GB18030-2000(GBK2K)。它收录了藏、蒙等少数民族的字型,从根本上解决了字位不足的问题。注意:它不再是定长的。其二字节部份与GBK兼容,四字节部分是扩充的字符、字形。它的首字节和第三字节从0x81到0xfe,二字节和第四字节从0x30到0x39。8 K" b  U, c" n! A, M: w- k% Y, i
. P, Y5 |- p* B5 Z; f3 V
  本文不打算介绍Unicode,有兴趣的可以浏览“http://www.unicode.org/”查看更多的信息。Unicode有一个特性:它包括了世界上所有的字符字形。所以,各个地区的语言都可以建立与Unicode的映射关系,而Java正是利用了这一点以达到异种语言之间的转换。- C) I) ^3 V. g7 g

" i4 ^  Q6 g7 B6 _  n  P  在JDK中,与中文相关的编码有:
& h) o3 L! d! D
/ z/ G3 M8 i- A- I" w. K# s" ]. {  表1 JDK中与中文相关的编码列表
$ }4 [4 l, J3 j# J3 T5 T5 Q3 p# S. E! Z8 d- l; e6 \6 ?& c2 k
编码名称说明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很少2 v, Y. O3 k' S' ]$ A0 W% {$ N
  在实际编程时,接触得比较多的是GB2312(GBK)和ISO8859-1。  W2 u8 Y; D: c# K+ k
# ]; ^4 \" c) p" h8 U
  为什么会有“?”号
/ V2 O& [8 {4 a5 ?( U* W# o! B3 y' K
  上文说过,异种语言之间的转换是通过Unicode来完成的。假设有两种不同的语言A和B,转换的步骤为:先把A转化为Unicode,再把Unicode转化为B。
9 u+ i+ z0 j4 F) r
2 F& m, O  o" b- O8 E# |  举例说明。有GB2312中有一个汉字“李”,其编码为“C0EE”,欲转化为ISO8859-1编码。步骤为:先把“李”字转化为Unicode,得到“674E”,再把“674E”转化为ISO8859-1字符。当然,这个映射不会成功,因为ISO8859-1中根本就没有与“674E”对应的字符。
; Z5 H; N5 x8 P" R! b5 `
  O0 G4 q: F2 j( F" Y$ g! h  当映射不成功时,问题就发生了!当从某语言向Unicode转化时,如果在某语言中没有该字符,得到的将是Unicode的代码“\uffffd”(“\u”表示是Unicode编码,)。而从Unicode向某语言转化时,如果某语言没有对应的字符,则得到的是“0x3f”(“?”)。这就是“?”的由来。
6 W0 l8 v# M6 T1 S
7 R1 B, }0 p, G3 U. Y( R  例如:把字符流buf =“0x80 0x40 0xb0 0xa1”进行new String(buf, "gb2312")操作,得到的结果是“\ufffd\u554a”,再println出来,得到的结果将是“?啊”,因为“0x80 0x40”是GBK中的字符,在GB2312中没有。; i; I8 i, e! k3 w# E) P) K0 Y
+ y2 a# |9 ?9 ]
  再如,把字符串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映射的内容中除了汉字外还有字符,本例就是最好的明证。
7 ^5 Q5 Z/ R7 w1 w2 D
, _2 I- a' R; [" {  所以,在汉字转码时,如果发生错乱,得到的不一定都是问号噢!不过,错了终究是错了,50步和100步并没有质的差别。) i: z9 _+ R" P1 `. h

1 U8 W$ z0 m) v8 D# z4 f+ m  或者会问:如果源字符集中有,而Unicode中没有,结果会如何?回答是不知道。因为我手头没有能做这个测试的源字符集。但有一点是肯定的,那就是源字符集不够规范。在Java中,如果发生这种情况,是会抛出异常的。
% a: Y" S0 w- _+ m4 T' H' l- M6 z  A9 X/ L. N; z) k% P5 W
什么是UTF
) \4 }7 y  h, g3 z. V: [6 g" X7 R9 i6 m: [4 k7 {  f9 C
  UTF,是Unicode Text Format的缩写,意为Unicode文本格式。对于UTF,是这样定义的:- X  G) b) A/ {6 U& E5 M7 R

- X  [2 Q- n5 ~& O7 t2 m8 K" C0 m  (1)如果Unicode的16位字符的头9位是0,则用一个字节表示,这个字节的首位是“0”,剩下的7位与原字符中的后7位相同,如“\u0034”(0000 0000 0011 0100),用“34” (0011 0100)表示;(与源Unicode字符是相同的);
' k+ g7 `( @& K: w. _# G, V$ V
4 X% u2 }6 l+ W$ C4 p# v  (2)如果Unicode的16位字符的头5位是0,则用2个字节表示,首字节是“110”开头,后面的5位与源字符中除去头5个零后的最高5位相同;第二个字节以“10”开头,后面的6位与源字符中的低6位相同。如“\u025d”(0000 0010 0101 1101),转化后为“c99d”(1100 1001 1001 1101);- T$ D" y( g. D

, {  m1 g  A3 V# F9 V  (3)如果不符合上述两个规则,则用三个字节表示。第一个字节以“1110”开头,后四位为源字符的高四位;第二个字节以“10”开头,后六位为源字符中间的六位;第三个字节以“10”开头,后六位为源字符的低六位;如“\u9da7”(1001 1101 1010 0111),转化为“e9b6a7”(1110 1001 1011 0110 1010 0111);5 ~" }6 Q, Y8 r0 b% J' s1 Z
+ s' i2 S- {$ Y- O2 J8 X. {0 M
  可以这么描述JAVA程序中Unicode与UTF的关系,虽然不绝对:字符串在内存中运行时,表现为Unicode代码,而当要保存到文件或其它介质中去时,用的是UTF。这个转化过程是由writeUTF和readUTF来完成的。
# g* m6 p  a9 T4 u, v+ |  ~4 Z$ n# `7 M. x- T8 G
  好了,基础性的论述差不多了,下面进入正题。
% W: v5 A& F, ?$ q! r7 [- ^3 z( q/ l0 a
  先把这个问题想成是一个黑匣子。先看黑匣子的一级表示:
. g. G2 v5 v8 x( Y" k
% K- x5 V/ w  e) |input(charsetA)->process(Unicode)->output(charsetB)1 P$ O* g  [8 e+ R6 b
# f' W  Z$ S$ X  x/ G) L$ F
  简单,这就是一个IPO模型,即输入、处理和输出。同样的内容要经过“从charsetA到unicode再到charsetB”的转化。4 V' P2 m5 T6 H- X

6 E8 s; s! L0 C  S$ _  再看二级表示:
6 X7 o! t: o4 @/ q! b  {" z) ?
" H# [+ M0 a2 o; WSourceFile(jsp,java)->class->output8 z  O8 _+ j* A  b& R
+ O+ i. F3 ^. s7 M! P7 ~
  在这个图中,可以看出,输入的是jsp和java源文件,在处理过程中,以Class文件为载体,然后输出。再细化到三级表示:% t& ]! U2 c  V0 A" l. q
! D8 v' Z9 }8 Z  b* J- |6 f! j2 j3 i
jsp->temp file->class->browser,os console,db; l0 [/ {3 ~; H  w

; H: k; B9 i$ x4 S! Dapp,servlet->class->browser,os console,db
5 T! X7 e% k9 b. k% b  B3 Z; Q) [4 s( S+ h; e
  这个图就更明白了。Jsp文件先生成中间的Java文件,再生成Class。而Servlet和普通App则直接编译生成Class。然后,从Class再输出到浏览器、控制台或数据库等。
, O, F7 C, I- k5 W; r: `  y
, O4 }- Y* j$ v% O; r0 j$ X  JSP:从源文件到Class的过程
1 D" d9 H! [0 u' h& i$ s* Z2 ?/ B& t8 f' p+ D. s! M
  Jsp的源文件是以“.jsp”结尾的文本文件。在本节中,将阐述JSP文件的解释和编译过程,并跟踪其中的中文变化。* y1 H& @8 r5 a( M" C/ a/ _. T
- Y+ n/ G, `) G; ]1 R( y6 w
  1、JSP/Servlet引擎提供的JSP转换工具(jspc)搜索JSP文件中用<%@ page contentType ="text/html; charset=<Jsp-charset>"%>中指定的charset。如果在JSP文件中未指定<Jsp-charset>,则取JVM中的默认设置file.encoding,一般情况下,这个值是ISO8859-1;
% L8 o9 U( n2 W7 m8 e, l4 B4 S; r8 e/ F
  2、jspc用相当于“javac –encoding <Jsp-charset>”的命令解释JSP文件中出现的所有字符,包括中文字符和ASCII字符,然后把这些字符转换成Unicode字符,再转化成UTF格式,存为JAVA文件。ASCII码字符转化为Unicode字符时只是简单地在前面加“00”,如“A”,转化为“\u0041”(不需要理由,Unicode的码表就是这么编的)。然后,经过到UTF的转换,又变回“41”了!这也就是可以使用普通文本编辑器查看由JSP生成的JAVA文件的原因;" _* f2 y/ b0 D  s. R
1 k; ~# |, N* O  d' [+ w9 i
  3、引擎用相当于“javac –encoding UNICODE”的命令,把JAVA文件编译成CLASS文件;
3 m9 V3 p) V' v6 I" u$ R! ~) R. b
  先看一下这些过程中中文字符的转换情况。有如下源代码:# r% T/ c* `. f# U9 H" H
9 V6 {3 z% i2 ]8 q" o% \
<%@ page contentType="text/html; charset=gb2312"%>9 K8 B( w8 d4 B; {! u4 O# ^# F
<html><body>
* F" U* k8 q; J<%
9 p; @' g# Y7 f# {  { String a="中文";% q* ]+ V) R$ O2 h0 G
 out.println(a);& M5 X. Y9 Z8 e& I# g( ?& n
%>7 Q" Q* R0 }: m, Z0 J3 C
</body></html>
0 S' Y: d' e4 t8 R  这段代码是在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文件中的完全一样。
# {, D! `- ^8 R& ~2 t4 o  {! T: L
" A) o5 C( _' J0 `; D  再看JSP中指定的CharSet为ISO-8859-1的情况。8 @% J2 x8 \6 E2 u& {7 S' {
) V" b, r2 S" U% J9 i

4 K  C; B' X. R, P# y<%@ page contentType="text/html; charset=ISO-8859-1"%>- g" w5 c* c4 J  K( q+ G
<html><body>
7 F+ ?+ F  i' K3 c, Y<%
4 Y0 x) ~' r: N7 ?4 V String a="中文";
8 t* ~7 s4 O0 U+ ` out.println(a);
  q( G: p$ V% Q5 q/ v%>3 e- n+ T! Y9 B; F0 T! Q7 A
</body></html>
9 F6 H8 V) ]6 q, w2 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”。( V. H3 g: p! {
7 J9 M( w: T8 O$ x
  如果上述代码中不指定<Jsp-charset>,即把第一行写成“<%@ page contentType="text/html" %>”,JSPC会使用file.encoding的设置来解释JSP文件。在RedHat 6.2上,其处理结果与指定为ISO-8859-1是完全相同的。
: [/ W* y/ T6 a: {. K1 h7 C, l- ?7 g* v* a
  到现在为止,已经解释了从JSP文件到CLASS文件的转变过程中中文字符的映射过程。一句话:从“JspCharSet到Unicode再到UTF”。下表总结了这个过程:' u7 P8 R# ]: m# t  W1 ~& ^

5 O7 k) o% U# ~7 n$ z! [  表2 “中文”从JSP到CLASS的转化过程
  A# @8 i' \% V* M% Z! q
6 U# q. \# P, r& S; z8 r9 ?( K1 I6 w( \, H2 z$ B1 c
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 C42 V1 }7 l/ ^4 f: H, t" ]7 ~
(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-10 R2 U" f1 y4 t. P9 w# @" h
  下节先讨论Servlet从JAVA文件到CLASS文件的转化过程,然后再解释从CLASS文件如何输出到客户端。之所以这样安排,是因为JSP和Servlet在输出时处理方法是一样的。
zan
转播转播0 分享淘帖0 分享分享0 收藏收藏0 支持支持0 反对反对0 微信微信
韩冰        

823

主题

3

听众

4048

积分

我的地盘我做主

该用户从未签到

发帖功臣 元老勋章

Servlet:从源文件到Class的过程; D  g9 Y7 g1 p, d5 P! O

$ _( {: U: t. R2 ~+ k  Servlet源文件是以“.java”结尾的文本文件。本节将讨论Servlet的编译过程并跟踪其中的中文变化。# o3 U0 s4 ~8 Q6 `. B
5 m: m7 E0 o, Z, t9 c
  用“javac”编译Servlet源文件。javac可以带“-encoding <Compile-charset>”参数,意思是“用< Compile-charset >中指定的编码来解释Serlvet源文件”。: X  |; V* }) m' _! D

1 ?: ?9 ^3 o# y  源文件在编译时,用<Compile-charset>来解释所有字符,包括中文字符和ASCII字符。然后把字符常量转变成Unicode字符,最后,把Unicode转变成UTF。( l3 h2 j5 F4 u' J/ u, n3 J/ B! q
/ p, @/ j% L! E
  在Servlet中,还有一个地方设置输出流的CharSet。通常在输出结果前,调用HttpServletResponse的setContentType方法来达到与在JSP中设置<Jsp-charset>一样的效果,称之为<Servlet-charset>。
$ O+ \0 x0 I8 \# a+ F5 z, l) g9 u' \
  注意,文中一共提到了三个变量:<Jsp-charset>、<Compile-charset>和<Servlet-charset>。其中,JSP文件只与<Jsp-charset>有关,而<Compile-charset>和<Servlet-charset>只与Servlet有关。
1 H$ l0 s5 |, \: ?" Y' A7 q$ Y3 U8 D$ c) P: B9 p  m$ W
  看下例:) }6 S+ j9 T& c$ c4 [. g

+ \+ a) Y+ ]  {$ z/ Vimport javax.servlet.*;
. n0 B4 p! B5 i# A! O, s
9 }7 X' z1 b( ~* n& Z) z# j& Vimport javax.servlet.http.*;
) _& K) |1 M. T9 `. s2 k+ l3 O- r6 g$ g1 H
class testServlet extends HttpServlet$ y4 a1 C( s' m; z# D! m1 w
{
) t2 k( p! Y+ g) E1 Q- o public void doGet(HttpServletRequest req,HttpServletResponse resp)0 ?8 k1 d1 }  a/ A5 Q( G
 throws ServletException,java.io.IOException9 ?! P! C# x& Y* I0 L) x
 {" e1 B% P" e6 y- Y( |
  resp.setContentType("text/html; charset=GB2312");8 f6 ~6 B8 g1 q) z3 k$ Z- S) ]9 ]
  java.io.PrintWriter out=resp.getWriter();
$ S0 _: n+ C- i0 e9 A2 ]  out.println("<html>");4 I1 d4 a' W* x3 k' [5 |) g' m
  out.println("#中文#");. A. |2 s' O9 o, h' @' q
  out.println("</html>");
/ w3 ]/ Y0 q/ \! K9 V$ X* b5 X }! Q3 V; s! V" b1 U; y: c/ V
}
) }0 B( I7 c& [8 e; D  该文件也是用UltraEdit for Windows编写的,其中的“中文”两个字保存为“D6 D0 CE C4”(GB2312编码)。; f8 @$ \" u; E; b
- r( W; o; C% ]3 h
  开始编译。下表是<Compile-charset>不同时,CLASS文件中“中文”两字的十六进制码。在编译过程中,<Servlet-charset>不起任何作用。<Servlet-charset>只对CLASS文件的输出产生影响,实际上是<Servlet-charset>和<Compile-charset>一起,达到与JSP文件中的<Jsp-charset>相同的效果,因为<Jsp-charset>对编译和CLASS文件的输出都会产生影响。1 _9 _6 f* u& Y. w
" u" s0 q, d4 j7 M
  表3 “中文”从Servlet源文件到Class的转变过程
5 D; N  u! N+ a( A, B
9 o4 N' N! I6 v7 g$ h% }% aCompile-charsetServlet源文件中Class文件中等效的Unicode码GB2312D6 D0 CE C4   ~2 M4 j9 d+ C/ |2 Y! g& |5 _
(GB2312)E4 B8 AD E6 96 87 (UTF)\u4E2D\u6587 (在Unicode中=“中文”)ISO-8859-1D6 D0 CE C4 5 Y/ F5 p7 ~5 x2 o4 M0 Y' [
(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" c# Q: c7 {2 C: i# P
  普通Java程序的编译过程与Servlet完全一样。/ f& I. v- d" ?0 L, ~

5 e2 I% }3 t! K9 H, F9 w  CLASS文件中的中文表示法是不是昭然若揭了?OK,接下来看看CLASS又是怎样输出中文的呢?  |5 X% k# c" c) U$ [2 o+ A

2 v* ]4 U* w4 c) z: W  Class:输出字符串5 R. z& A6 q. Q6 s
6 x# f9 m0 d3 V3 x  j& u( i; ]* z
  上文说过,字符串在内存中表现为Unicode编码。至于这种Unicode编码表示了什么,那要看它是从哪种字符集映射过来的,也就是说要看它的祖先。这好比在托运行李时,外观都是纸箱子,里面装了什么就要看寄邮件的人实际邮了什么东西。9 L$ w2 g! Y+ ]; Y- P2 ^  B+ S
# }' [) g1 b; S5 r
  看看上面的例子,如果给一串Unicode编码“00D6 00D0 00CE 00C4”,如果不作转换,直接用Unicode码表来对照它时,是四个字符(而且是特殊字符);假如把它与“ISO8859-1”进行映射,则直接去掉前面的“00”即可得到“D6 D0 CE C4”,这是ASCII码表中的四个字符;而假如把它当作GB2312来进行映射,得到的结果很可能是一大堆乱码,因为在GB2312中有可能没有(也有可能有)字符与00D6等字符对应(如果对应不上,将得到0x3f,也就是问号,如果对应上了,由于00D6等字符太靠前,估计也是一些特殊符号,真正的汉字在Unicode中的编码从4E00开始)。; G. D9 p% p. V9 ]/ U" p' ^( Q
. P2 b" }( j  v
  各位看到了,同样的Unicode字符,可以解释成不同的样子。当然,这其中有一种是我们期望的结果。以上例而论,“D6 D0 CE C4”应该是我们所想要的,当把“D6 D0 CE C4”输出到IE中时,用“简体中文”方式查看,就能看到清楚的“中文”两个字了。(当然了,如果你一定要用“西欧字符”来看,那也没办法,你将得不到任何有何时何地的东西)为什么呢?因为“00D6 00D0 00CE 00C4”本来就是由ISO8859-1转化过去的。
, n, E: r7 v8 j2 W7 p) \
* P& ?/ g0 T# |& `2 V  j给出如下结论:
7 F) k; H  u* b! L" _7 v* s% ]! O
  在Class输出字符串前,会将Unicode的字符串按照某一种内码重新生成字节流,然后把字节流输入,相当于进行了一步“String.getBytes(???)”操作。???代表某一种字符集。" Z- S6 ^/ }! E& x! G# p2 N: z

7 B' e* j7 h- N  如果是Servlet,那么,这种内码就是在HttpServletResponse.setContentType()方法中指定的内码,也就是上文定义的<Servlet-charset>。+ s& R$ C7 \6 l) v; }1 e& \

, ^) {5 m) u$ m  \  如果是JSP,那么,这种内码就是在<%@ page contentType=""%>中指定的内码,也就是上文定义的<Jsp-charset>。
5 \0 h- D5 h' [
8 s# u  \7 q# f9 ^2 e; O# i% Q" o  如果是Java程序,那么,这种内码就是file.encoding中指定的内码,默认为ISO8859-1。5 `. h6 x8 b  ^6 a$ G1 D2 l
9 F4 `2 x8 V! J- p/ z
  当输出对象是浏览器时
6 h/ M) O5 u/ k5 w8 v
$ S) ^5 G  X2 z7 j  以流行的浏览器IE为例。IE支持多种内码。假如IE接收到了一个字节流“D6 D0 CE C4”,你可以尝试用各种内码去查看。你会发现用“简体中文”时能得到正确的结果。因为“D6 D0 CE C4”本来就是简体中文中“中文”两个字的编码。
, Y* a$ e1 b- O. P* n, B0 E5 L  d
  OK,完整地看一遍。, j/ g( j* H2 z( p4 J
- w9 S' x. Q$ v7 j
  JSP:源文件为GB2312格式的文本文件,且JSP源文件中有“中文”这两个汉字
0 `8 i5 n; K" w3 k# t0 D: m, q% u9 y$ j( }
  如果指定了<Jsp-charset>为GB2312,转化过程如下表。- p% ]$ H: K; P

" w0 i& J; |" t: s  表4 Jsp-charset = GB2312时的变化过程
- x1 i( b, v* Z2 Q" j6 @* M; U  B7 c0 P
序号步骤说明结果1编写JSP源文件,且存为GB2312格式D6 D0 CE C4) l% L  R' r- Y5 J( k
(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用“简体中文”查看结果“中文”(正确显示)# W* {0 e9 k" C2 c* _
  如果指定了<Jsp-charset>为ISO8859-1,转化过程如下表。
+ H& }- l1 P  X. ?- _/ [2 {3 _0 w- g  F4 t3 ~
  表5 Jsp-charset = ISO8859-1时的变化过程6 ~7 o7 [; y9 I) _

/ F8 i' f5 C3 V8 A/ y& d1 N6 o序号步骤说明结果1编写JSP源文件,且存为GB2312格式D6 D0 CE C4
' m7 n+ a1 z) ], H3 j/ F# u(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
7 T& n9 c8 v" u- m(啥都不是!!!)5根据Jsp-charset=ISO8859-1把Unicode转化为字节流D6 D0 CE C46把字节流输出到IE中,并设置IE的编码为ISO8859-1(作者按:这个信息隐藏在HTTP头中)D6 D0 CE C47IE用“西欧字符”查看结果乱码,其实是四个ASCII字符,但由于大于128,所以显示出来的怪模怪样8改变IE的页面编码为“简体中文”“中文”(正确显示)
! T4 w# L! M. |. e+ X3 L$ T1 A" i  奇怪了!为什么把<Jsp-charset>设成GB2312和ISO8859-1是一个样的,都能正确显示?因为表4表5中的第2步和第5步互逆,是相互“抵消”的。只不过当指定为ISO8859-1时,要增加第8步操作,殊为不便。1 w) q( D- F  h7 K$ Y
" [* t8 R( ~8 C  C; a2 X
  再看看不指定<Jsp-charset> 时的情况。; }4 L4 N! R( _+ W) b0 r, n, r) z

1 g0 U! F, V" n. W, c  表6 未指定Jsp-charset 时的变化过程' C) `/ G5 V9 `. k1 D

& l" N  t$ N0 l2 v' @) r1 _序号步骤说明结果1编写JSP源文件,且存为GB2312格式D6 D0 CE C4
+ d6 y5 d! S+ c+ E, q0 o. P(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步( B: K. m9 H& |+ p6 }9 J; C
  Servlet:源文件为JAVA文件,格式是GB2312,源文件中含有“中文”这两个汉字4 N+ B2 c8 a1 M% C; a5 b# F
! {5 T1 M) D6 L; v* \
  如果<Compile-charset>=GB2312,<Servlet-charset>=GB23129 n3 _, }% d( [/ D  F5 r0 P- ]3 G

: G. I9 v5 {! {  表7 Compile-charset=Servlet-charset=GB2312 时的变化过程6 p5 ?  E( I: t5 g0 Q# W

, W4 b/ f4 {$ r% _( `+ Q0 c序号步骤说明结果1编写Servlet源文件,且存为GB2312格式D6 D0 CE C4# c& y/ N; l& k  g
(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用“简体中文”查看结果“中文”(正确显示)- N7 E- n* a# Q3 o" w. X% ]! d  p& a
  如果<Compile-charset>=ISO8859-1,<Servlet-charset>=ISO8859-1
$ X$ g9 @4 ~+ B. e: }% a$ E1 l# p5 |1 D1 T  n  u! r
  表8 Compile-charset=Servlet-charset=ISO8859-1时的变化过程
  e5 m) o5 y6 ?  v
( W/ `' b" Y% g# p1 w' G序号步骤说明结果1编写Servlet源文件,且存为GB2312格式D6 D0 CE C4, |+ ~2 k5 X7 x0 l4 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的页面编码为“简体中文”“中文”(正确显示)" _' g1 U$ l, O
  如果不指定Compile-charset或Servlet-charset,其默认值均为ISO8859-1。- V( y# y$ i- e& W! ]
! h' _2 V! E2 K- z* U- L
  当Compile-charset=Servlet-charset时,第2步和第4步能互逆,“抵消”,显示结果均能正确。读者可试着写一下Compile-charset<>Servlet-charset时的情况,肯定是不正确的。
7 ]0 g7 N! E7 o" [7 P  B/ `- k3 j: o; P& N
  当输出对象是数据库时& E/ ~" z* \2 U7 V
( R, E" x( ~8 P( }! p
  输出到数据库时,原理与输出到浏览器也是一样的。本节只是Servlet为例,JSP的情况请读者自行推导。% V7 S! E- R$ R  i, q
1 l  R$ q, t( w& i% |
  假设有一个Servlet,它能接收来自客户端(IE,简体中文)的汉字字符串,然后把它写入到内码为ISO8859-1的数据库中,然后再从数据库中取出这个字符串,显示到客户端。
; S) v* \3 y  y" m. {' s! k& r; H% E1 Q5 \$ ?; Y
  表9 输出对象是数据库时的变化过程(1)
9 a  G- Z0 B# J
. T3 M3 Q8 q7 _序号步骤说明结果域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
, L( a. u8 z. b( Q: B4 ]2 J(Unicode) Servlet准备把字符串输出到客户端16Servlet根据<Servlet-charset>生成字节流D6D0 CE C4Servlet17Servlet把字节流输出到IE中,如果已指定<Servlet-charset>,还会设置IE的编码为<Servlet-charset>D6 D0 CE C418IE根据指定的编码或默认编码查看结果“中文”(正确显示)IE
" O6 _( I9 t2 F& L) ]/ E5 d  解释一下,表中第4第5步和第15第16步是用红色标记的,表示要由编码者来作转换。第4、5两步其实就是一句话:“new String(source.getBytes("GB2312"), "ISO8859-1")”。第15、16两步也是一句话:“new String(source.getBytes("ISO8859-1"), "GB2312")”。亲爱的读者,你在这样编写代码时是否意识到了其中的每一个细节呢?( Y& D- g9 u$ f# M1 _0 `
5 w5 E6 f- A$ Z. A0 g
  至于客户端内码和数据库内码为其它值时的流程,和输出对象是系统控制台时的流程,请读者自己想吧。明白了上述流程的原理,相信你可以轻松地写出来。
/ T9 B' n; {; s5 i5 @
4 u$ E7 E, z* K, N3 \+ t5 p  行文至此,已可告一段落了。终点又回到了起点,对于编程者而言,几乎是什么影响都没有。$ Z# l& J9 P7 n: ^% a1 D) Y0 u
, d  L- Y. F' F' O) R. k/ F
  因为我们早就被告之要这么做了。
, `, f5 r; e7 L7 v! c5 ~1 R! |" u0 j$ [& V
  以下给出一个结论,作为结尾。
' }4 Q/ V( T5 z! |+ K; N9 A) r( M
. q9 Z! x( v2 L  1、 在Jsp文件中,要指定contentType,其中,charset的值要与客户端浏览器所用的字符集一样;对于其中的字符串常量,不需做任何内码转换;对于字符串变量,要求能根据ContentType中指定的字符集还原成客户端能识别的字节流,简单地说,就是“字符串变量是基于<Jsp-charset>字符集的”;! B  y$ s  P! A  C" P

) N& T; @& c9 J/ ?% Y) `/ ~! q  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-19 14:20 , Processed in 0.417961 second(s), 57 queries .

回顶部