溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×

JSP和Servlet對(duì)中文的處理方式

發(fā)布時(shí)間:2021-07-05 16:04:08 來(lái)源:億速云 閱讀:190 作者:chen 欄目:編程語(yǔ)言

這篇文章主要講解了“JSP和Servlet對(duì)中文的處理方式”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來(lái)研究和學(xué)習(xí)“JSP和Servlet對(duì)中文的處理方式”吧!

深入剖析JSP和Servlet對(duì)中文的處理


Java程序員都曾遇到過(guò)這樣的問(wèn)題:輸入的中文不能正確顯示在界面上,保存在數(shù)據(jù)庫(kù)中的也是一堆亂碼,或者數(shù)據(jù)庫(kù)或數(shù)據(jù)文件中存放的是正確的中文,可是在Java程序中看到的卻是一大串的“?”。

這就是通常所說(shuō)的“中文問(wèn)題”。

Java中與中文相關(guān)的編碼


在JDK中,提供了對(duì)大多數(shù)常用語(yǔ)言的支持。在解決“中文問(wèn)題”時(shí),表1中的編碼是最常用,或者就是最有關(guān)系的。

表1 JDK中與中文相關(guān)的編碼列表


編碼名稱(chēng)說(shuō)明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相同,注意:區(qū)分大小寫(xiě)UTF與URF-8相同GB18030與cp1392、1392相同,目前支持的JDK很少



在實(shí)際編程時(shí),接觸得比較多的是GB2312(GBK)和ISO8859-1。

在實(shí)際編程時(shí),接觸得比較多的是GB2312(GBK)和ISO8859-1。

  為什么會(huì)有“?”號(hào)

  上文說(shuō)過(guò),異種語(yǔ)言之間的轉(zhuǎn)換是通過(guò)Unicode來(lái)完成的。假設(shè)有兩種不同的語(yǔ)言A和B,轉(zhuǎn)換的步驟為:先把A轉(zhuǎn)化為Unicode,再把Unicode轉(zhuǎn)化為B。

  舉例說(shuō)明。有GB2312中有一個(gè)漢字“李”,其編碼為“C0EE”,欲轉(zhuǎn)化為ISO8859-1編碼。步驟為:先把“李”字轉(zhuǎn)化為Unicode,得到“674E”,再把“674E”轉(zhuǎn)化為ISO8859-1字符。當(dāng)然,這個(gè)映射不會(huì)成功,因?yàn)镮SO8859-1中根本就沒(méi)有與“674E”對(duì)應(yīng)的字符。

  當(dāng)映射不成功時(shí),問(wèn)題就發(fā)生了!當(dāng)從某語(yǔ)言向Unicode轉(zhuǎn)化時(shí),如果在某語(yǔ)言中沒(méi)有該字符,得到的將是Unicode的代碼“uffffd”(“u”表示是Unicode編碼,)。而從Unicode向某語(yǔ)言轉(zhuǎn)化時(shí),如果某語(yǔ)言沒(méi)有對(duì)應(yīng)的字符,則得到的是“0x3f”(“?”)。這就是“?”的由來(lái)。

  例如:把字符流buf =“0x80 0x40 0xb0 0xa1”進(jìn)行new String(buf, "gb2312")操作,得到的結(jié)果是“ufffdu554a”,再println出來(lái),得到的結(jié)果將是“?啊”,因?yàn)椤?x80 0x40”是GBK中的字符,在GB2312中沒(méi)有。

  再如,把字符串String="u00d6u00ecu00e9u0046u00bbu00f9"進(jìn)行new String (buf.getBytes("GBK"))操作,得到的結(jié)果是“3fa8aca8a6463fa8b4”,其中,“u00d6”在“GBK”中沒(méi)有對(duì)應(yīng)的字符,得到“3f”,“u00ec”對(duì)應(yīng)著“a8ac”,“u00e9”對(duì)應(yīng)著“a8a6”,“0046”對(duì)應(yīng)著“46”(因?yàn)檫@是ASCII字符),“u00bb”沒(méi)找到,得到“3f”,最后,“u00f9”對(duì)應(yīng)著“a8b4”。把這個(gè)字符串println一下,得到的結(jié)果是“?ìéF?ù”??吹?jīng)]?這里并不全是問(wèn)號(hào),因?yàn)镚BK與Unicode映射的內(nèi)容中除了漢字外還有字符,本例就是最好的明證。

  所以,在漢字轉(zhuǎn)碼時(shí),如果發(fā)生錯(cuò)亂,得到的不一定都是問(wèn)號(hào)噢!不過(guò),錯(cuò)了終究是錯(cuò)了,50步和100步并沒(méi)有質(zhì)的差別。

  或者會(huì)問(wèn):如果源字符集中有,而Unicode中沒(méi)有,結(jié)果會(huì)如何?回答是不知道。因?yàn)槲沂诸^沒(méi)有能做這個(gè)測(cè)試的源字符集。但有一點(diǎn)是肯定的,那就是源字符集不夠規(guī)范。在Java中,如果發(fā)生這種情況,是會(huì)拋出異常的。
  什么是UTF

  UTF,是Unicode Text Format的縮寫(xiě),意為Unicode文本格式。對(duì)于UTF,是這樣定義的:

 ?。?)如果Unicode的16位字符的頭9位是0,則用一個(gè)字節(jié)表示,這個(gè)字節(jié)的首位是“0”,剩下的7位與原字符中的后7位相同,如“u0034”(0000 0000 0011 0100),用“34” (0011 0100)表示;(與源Unicode字符是相同的);

 ?。?)如果Unicode的16位字符的頭5位是0,則用2個(gè)字節(jié)表示,首字節(jié)是“110”開(kāi)頭,后面的5位與源字符中除去頭5個(gè)零后的最高5位相同;第二個(gè)字節(jié)以“10”開(kāi)頭,后面的6位與源字符中的低6位相同。如“u025d”(0000 0010 0101 1101),轉(zhuǎn)化后為“c99d”(1100 1001 1001 1101);

 ?。?)如果不符合上述兩個(gè)規(guī)則,則用三個(gè)字節(jié)表示。第一個(gè)字節(jié)以“1110”開(kāi)頭,后四位為源字符的高四位;第二個(gè)字節(jié)以“10”開(kāi)頭,后六位為源字符中間的六位;第三個(gè)字節(jié)以“10”開(kāi)頭,后六位為源字符的低六位;如“u9da7”(1001 1101 1010 0111),轉(zhuǎn)化為“e9b6a7”(1110 1001 1011 0110 1010 0111);

注:UTF是Unicode Transformation Format的縮寫(xiě),意為Unicode轉(zhuǎn)換格式??梢赃@么描述JAVA程序中Unicode與UTF的關(guān)系,雖然不絕對(duì)。字符串在內(nèi)存中運(yùn)行時(shí),表現(xiàn)為Unicode代碼,而當(dāng)要保存到文件或其它介質(zhì)中去時(shí),用的是UTF。這個(gè)轉(zhuǎn)化過(guò)程是由writeUTF和readUTF來(lái)完成得。

Servlet/JSP對(duì)中文的處理過(guò)程


總體流程

把問(wèn)題想成是一個(gè)黑匣子。先看黑匣子的一級(jí)表示(如圖1所示):

JSP和Servlet對(duì)中文的處理方式


圖1 IPO模型


這就是一個(gè)IPO模型,即輸入、處理和輸出。同樣的內(nèi)容要經(jīng)過(guò)“從charsetA到Unicode再到charsetB”的轉(zhuǎn)化。

再看二級(jí)表示(如圖2所示):

JSP和Servlet對(duì)中文的處理方式


圖2 JSP、Java輸出模型


在這個(gè)圖中,輸入的是JSP和Java源文件。在處理過(guò)程中,以Class文件為載體,然后輸出。再細(xì)化到三級(jí)(如圖3所示):

JSP和Servlet對(duì)中文的處理方式


圖3 IPO模型


JSP文件先生成中間的Java文件,再生成Class。而Servlet和普通App則直接編譯生成Class,然后,從Class再輸出到瀏覽器、控制臺(tái)或數(shù)據(jù)庫(kù)等。

JSP:從源文件到Class的過(guò)程

JSP源文件是以“.jsp”結(jié)尾的文本文件。在本節(jié)中,將闡述JSP文件的解釋和編譯過(guò)程,并跟蹤其中中文內(nèi)容的變化。

一般地,JSP源文件經(jīng)過(guò)如下步驟后變成可被引擎執(zhí)行的Class文件:

1. JSP/Servlet引擎提供的JSP轉(zhuǎn)換工具(JSPC)搜索JSP文件中用<%@ page="" contenttype="text/html; charset=<Jsp-charset>">中指定的charset。如果在JSP文件中未指定,則默認(rèn)為ISO8859-1(或者說(shuō)是Latin-1)。

2. JSPC用相當(dāng)于“Javac -encoding”解釋JSP文件中出現(xiàn)的所有字符,包括中文字符和ASCII字符。然后把這些字符轉(zhuǎn)換成Unicode字符,再轉(zhuǎn)化成UTF格式,存為Java文件。ASCII碼字符轉(zhuǎn)化為Unicode字符時(shí)只是簡(jiǎn)單地在前面加“00”,如“A”,轉(zhuǎn)化為“u0041”。然后,經(jīng)過(guò)了UTF的轉(zhuǎn)換,又變回“41”了。這也就是可以使用普通文本編輯器查看由JSP生成的Java文件的原因。

3. 引擎用相當(dāng)于“Javac -encoding UTF-8”的命令,把Java文件編譯成Class文件。

先看一下這些過(guò)程中中文字符的轉(zhuǎn)換情況。有如下源代碼:



這段代碼是在UltraEdit for Windows上編寫(xiě)的。保存后,“中文”兩個(gè)字的16進(jìn)制編碼為“D6 D0 CE C4”(GB2312編碼)。經(jīng)查表,“中文”兩字的Unicode編碼為“u4E2Du6587”,用 UTF表示就是“E4 B8 AD E6 96 87”。打開(kāi)引擎生成的由JSP文件轉(zhuǎn)變成的Java文件,發(fā)現(xiàn)其中的“中文”兩個(gè)字的位置確實(shí)被“E4 B8 AD E6 96 87”替代了,再查看由Java文件編譯生成的Class文件,發(fā)現(xiàn)結(jié)果與Java文件中的完全一樣,也是“E4 B8 AD E6 96 87”。

再看JSP中指定的CharSet為ISO-8859-1的情況:



同樣,該文件是用UltraEdit編寫(xiě)的?!爸形摹边@兩個(gè)字也是存為GB2312編碼“D6 D0 CE C4”。先模擬一下生成的Java文件和Class文件的過(guò)程:JSPC用ISO-8859-1來(lái)解釋“中文”,并把它映射到Unicode。由于ISO-8859-1是8位的,且是拉丁語(yǔ)系,其映射規(guī)則就是在每個(gè)字節(jié)前加“00”。所以,映射后的Unicode編碼應(yīng)為“u00D6u00D0 u00CEu00C4”,轉(zhuǎn)化成UTF后應(yīng)該是“C3 96 C3 90 C3 8E C3 84”。好,打開(kāi)文件Java文件和CLASS文件,“中文”兩個(gè)字的位置果然都表示為“C3 96 C3 90 C3 8E C3 84”。

如果上述代碼中不指定,即把第一行寫(xiě)成“<%@ page="" contenttype="text/html">”,JSPC會(huì)使用默認(rèn)的“ISO8859-1”來(lái)解釋JSP文件。

到現(xiàn)在為止,已經(jīng)解釋了從JSP文件到Class文件的轉(zhuǎn)變過(guò)程中中文字符的映射過(guò)程。一句話(huà),從“Jsp-CharSet到Unicode再到UTF”。表2總結(jié)了這個(gè)過(guò)程:

表2 “中文”從JSP到Class的轉(zhuǎn)化過(guò)程


Jsp-CharSetJSP文件中JAVA文件中CLASS文件中GB2312D6 DO CE C4(GB23112)從u4E2Du6587(Unicode)到E4 B8(UTF)E4 B8 AD E6 96 87 (UTF)ISO-8859-1D6 D0 CE C4 (GB2312)從u00D6u00D0u00CEu00C4(Unicode)到C3 96 C3 90C3 8E C3 84 (UTF)C3 96 C3 908E C3 C3 8EC3 84 (UTF)無(wú)(默認(rèn)=file.encoding)同ISO-8859-1同ISO-8859-1同ISO-8859-1
Servlet:從源文件到Class的過(guò)程

Servlet源文件是以“.Java”結(jié)尾的文本文件。我們將討論Servlet的編譯過(guò)程并跟蹤其中的中文變化。

用“Javac”編譯Servlet源文件。Javac可以帶“-encoding”參數(shù),意思是“用< Compile-charset >中指定的編碼來(lái)解釋Serlvet源文件”。

源文件在編譯時(shí),用來(lái)解釋所有字符,包括中文字符和ASCII字符。然后把字符常量轉(zhuǎn)變成Unicode字符。最后,把Unicode轉(zhuǎn)變成UTF。

在Servlet中,還有一個(gè)地方設(shè)置輸出流的CharSet。通常在輸出結(jié)果前,調(diào)用HttpServletResponse的setContent Type方法來(lái)達(dá)到與在JSP中設(shè)置一樣的效果,稱(chēng)之為。

注意:文中一共提到了三個(gè)變量:、。其中,JSP文件只與有關(guān),而只與Servlet有關(guān)。

看下例:

import Javax.servlet.*; import Javax.servlet.http.*; Class testServlet extends HttpServlet { public void doGet(HttpServletRequest req,HttpServletResponse resp) throws ServletException,Java.io.IOException { resp.setContentType("text/html; charset=GB2312"); Java.io.PrintWriter out=resp.getWriter(); out.println(""); out.println("#中文#"); out.println(""); } }

該文件也是用UltraEdit for Windows編寫(xiě)的,其中的“中文”兩個(gè)字保存為字節(jié)流“D6 D0 CE C4”(GB2312編碼)。

開(kāi)始編譯。表3是不同時(shí),Class文件中“中文”兩字的十六進(jìn)制碼。在編譯過(guò)程中,不起任何作用。只對(duì)Class文件的輸出產(chǎn)生影響,可以說(shuō)一起,達(dá)到與JSP文件中的相同的效果,因?yàn)?jsp-charset>對(duì)編譯過(guò)程和Class文件的輸出都會(huì)產(chǎn)生影響。

表3 “中文”從Servlet源文件到Class的轉(zhuǎn)變過(guò)程


Compile-charsetServlet源文件中Class文件中等效的Unicode碼GB2312D6 D0 CE C4(GB2312)E4 B8 AD E6 96 87(UTF)u4E2Du6587(在Unicode中=“中文”)ISO-8859-1D6 D0 CE C4(GB2312)C3 96 C3 90C3 8E C3 84(UTF)u00d6u00D0u00CEu00C4(在D6 D0 CE C4前面各加了一個(gè)00)無(wú)(默認(rèn))D6 D0 CE C4(GB2312)同ISO-8859-1同ISO-8859-1



注意:普通Java程序的編譯過(guò)程與Servlet完全一樣。

截止現(xiàn)在,從JSP或Servlet的源文件到Class文件的過(guò)程中中文內(nèi)容的蛻變歷程是不是昭然若揭了?OK,接下來(lái)看看Class文件中的中文又是怎樣被輸出的呢?

Class:輸出字符串

Class文件是Java程序的一種存儲(chǔ)載體。當(dāng)Class文件被虛擬機(jī)執(zhí)行時(shí),通過(guò)readUTF把Class文件中的內(nèi)容讀入內(nèi)存中。字符串在內(nèi)存中表示為Unicode編碼。當(dāng)要把內(nèi)存中的內(nèi)容輸出到別的程序或是外圍設(shè)備(如終端)上去時(shí),問(wèn)題就來(lái)了(為了簡(jiǎn)單起見(jiàn),把“別的程序或外圍設(shè)備”稱(chēng)之為“輸出對(duì)象”)。

1.如果輸出對(duì)象能處理Unicode字符,則一切都很簡(jiǎn)單,只要把Unicode字符直接傳給輸出對(duì)象即可。

2.事實(shí)是,大多數(shù)輸出對(duì)象不能直接處理Unicode,它們只能處理ISO8859-1和GB2312等。在往輸出對(duì)象輸出字符串時(shí),需要做一定的轉(zhuǎn)換才行。

看看下面的例子,給定一個(gè)有四個(gè)字符的Unicode字符串“00D6 00D0 00CE 00C4”,如果輸出到只能識(shí)別“ISO8859-1”的程序中去,則直接去掉前面的“00”即可得到目的字符串“D6 D0 CE C4”。假如把它們輸出到GB2312的程序中去,得到的結(jié)果很可能是一大堆亂碼。因?yàn)樵贕B2312中可能沒(méi)有(也有可能有)字符與00D6等字符對(duì)應(yīng)(如果對(duì)應(yīng)不上,將得到0x3f,也就是問(wèn)號(hào),如果對(duì)應(yīng)上了,由于00D6等字符太靠前,估計(jì)也是一些特殊符號(hào),真正的漢字在Unicode中的編碼從4E00開(kāi)始)。

同樣的Unicode字符,輸出到不同編碼的對(duì)象中去時(shí),結(jié)果是不同的。當(dāng)然,這其中有一種是我們期望的結(jié)果。對(duì)于能處理中文的輸出對(duì)象而言,自然希望輸入的內(nèi)容(也就是Java程序輸出的內(nèi)容)是基于GB2312編碼有意義的中文字符串。

以上例而論,“D6 D0 CE C4”應(yīng)該是我們所想要的。當(dāng)把“D6 D0 CE C4”輸出到IE中時(shí),用“簡(jiǎn)體中文”方式查看,就能看到清楚的“中文”兩個(gè)字了。

得出如下結(jié)論:

Java程序在輸出字符串前,必須先把Unicode的字符串按照某一種內(nèi)碼重新生成字節(jié)流,然后把字節(jié)流輸出給“輸出對(duì)象”,相當(dāng)于進(jìn)行了一步“String.getBytes(???)”操作,其中???代表一種字符集的名字。

1.如果是Servlet,這種字符集是在HttpServlet Response.setContentType()方法中指定的,也就是上文定義的

2. 如果是JSP,這種字符集是在<%@ page="" content="" type="">中指定的,也就是上文定義的

3. 如果是Java程序,這種字符集是由file.encoding中指定的,默認(rèn)為ISO8859-1。

當(dāng)輸出對(duì)象是瀏覽器時(shí)

以流行的瀏覽器IE為例。IE支持多種字符集。假如IE接收到了字節(jié)流“D6 D0 CE C4”,你可以嘗試用各種內(nèi)碼去查看。你會(huì)發(fā)現(xiàn)用“簡(jiǎn)體中文”時(shí)能得到正確的結(jié)果。因?yàn)椤癉6 D0 CE C4”本來(lái)就是簡(jiǎn)體中文中“中文”兩個(gè)字的編碼。

OK,完整地看一遍JSP和Servlet中,中文內(nèi)容的變化細(xì)節(jié)。

從JSP源文件到瀏覽器

前提:JSP源文件為GB2312格式的文本文件,且JSP源文件中有“中文”這兩個(gè)漢字

如果指定了為GB2312,轉(zhuǎn)化過(guò)程如表4。

表4 Jsp-charset=GB2312時(shí)的變化過(guò)程


序號(hào)步驟說(shuō)明結(jié)果1編寫(xiě)JSP源文件,且存為GB2312格式D6 D0 CE C4(D6D0=中 CEC4=文)2JSPC把JSP源文件轉(zhuǎn)化為臨時(shí)Java文件,并把字符串按照GB2312映射到Unicode,并用UTF格式寫(xiě)入Java文件中E4 B8 AD E6 96 873把臨時(shí)Java文件編譯成Class文件E4 B8 AD E6 96 874運(yùn)行時(shí),先從Class文件中用readUTF讀出字符串,在內(nèi)存中的是Unicode編碼4E 2D 65 87 (在Unicode中4E2D=中 6587=文)5根據(jù)Jsp-charset=GB2312 把Unicode轉(zhuǎn)化為字節(jié)流D6 D0 CE C46把字節(jié)流輸出到IE中,并設(shè)置IE的編碼為GB2312(作者按:這個(gè)信息隱藏在HTTP頭中)D6 D0 CE C47IE用“簡(jiǎn)體中文”查看結(jié)果“中文”(正確顯示)



如果指定了為ISO8859-1,轉(zhuǎn)化過(guò)程如表5。

表5 Jsp-charset=ISO8859-1時(shí)的變化過(guò)程


序號(hào)步驟說(shuō)明結(jié)果1編寫(xiě)JSP源文件,且存為GB2312格式D6 D0 CE C4(D6D0=中 CEC4=文)2JSPC把JSp源文件轉(zhuǎn)化為臨時(shí)Java文件,并把字符串按照ISO8859-1映射到Unicode,并用UTF格式寫(xiě)入Java文件中C3 96 C3 90 C3 8E C3 843把臨時(shí)Java文件編譯成Class文件C3 96 C3 90 C3 BE C3 844運(yùn)行時(shí),先從Class文件中用readUTF讀出字符串,在內(nèi)存中的是Unicode編碼00 D6 00 D0 00 CE 00 C4(哈哈都不是?。?!)5根據(jù)Jsp-charset=ISO8859-1把Unicode轉(zhuǎn)花為字節(jié)流D6 D0 CE C46把字節(jié)流輸出到IE中,并設(shè)置IE的編碼為ISO8859-1(作者按:這個(gè)信息隱藏在HTTP頭中)D6 D0 CE C47IE用“西歐字符”查看結(jié)果(注:西歐字符與ISO8859-1是相對(duì)應(yīng)的)亂碼,啟示時(shí)四個(gè)ASCII字符,但由于大于128,所以顯示出來(lái)得怪模怪樣8改變IE的頁(yè)面編碼為“簡(jiǎn)體中文”“中文”(正確顯示)
奇怪了!為什么把設(shè)成GB2312和ISO8859-1是一個(gè)樣的,都能正確顯示?因?yàn)楸?、表5中的第2步和第5步互逆,是相互“抵消”的。只不過(guò)當(dāng)指定為ISO8859-1時(shí),要增加第8步操作,殊為不便。

通過(guò)表6再看看不指定時(shí)的情況。

表6 未指定Jsp-charset時(shí)的變化過(guò)程


序號(hào)步驟說(shuō)明結(jié)果1編寫(xiě)JSP源文件,且存為GB2312格式D6 D0 CE C4(D6D0=中 CEC4=文)2JSPC把JSP源文件轉(zhuǎn)化為臨時(shí)Java文件,并把字符串按照ISO8859-1映射到Unicode,并用UTF格式寫(xiě)入Java文件中C3 96 C390 C3 8E C3 843把臨時(shí)Java文件編譯成Class文件C3 96 C390 C3 8E C3844運(yùn)行事,先從Class文件中用readUTF 讀出字符串,在內(nèi)存中的是Unicode編碼00 D6 00 D000 CE 00 C4(哈都不是?。。。?根據(jù)Jsp-charset=ISO8859-1把Unicode轉(zhuǎn)化為字節(jié)流D6 D0 CE C46把字節(jié)流輸出到IE中D6 D0 CE C47IE用發(fā)出請(qǐng)求時(shí)的頁(yè)面的編碼查看結(jié)果視情況而定。如果是簡(jiǎn)體中文,則能正確顯示,否則需執(zhí)行表5中的第8步



從Servlet源文件到瀏覽器

前提:Servlet源文件為Java文件,格式是GB2312,且含有“中文”這兩個(gè)漢字。

如果=GB2312,則=GB2312(見(jiàn)表7)。

表7 Compile-Charset=Servlet-charset=GB2312時(shí)的變化過(guò)程


序號(hào)步驟說(shuō)明結(jié)果1編寫(xiě)Servlet源文件,且存為GB2312格式D6 D0 CE C4(D6D0=中 CEC4=文)2由于Compole-charset是GB2312,所以用Java-encoding GB2312把JAVA源文件編譯成Class文件E4 B8 AD E6 96 87(UTF)3運(yùn)行時(shí),先從Class文件中用readUTF讀出字符串,在內(nèi)存中的是Unicode編碼4E 2D 65 87(Unicode)4根據(jù)Servlet-charset=GB2312把Unicode轉(zhuǎn)化為字節(jié)流D6 D0 CE C4(GB2312)5把字節(jié)流輸出到IE中并設(shè)置為IE的編碼屬性為Servlet-charset=GB2312D6 D0 CE C4(GB2312)6IE用“簡(jiǎn)體中文”查看結(jié)果“中文”(正確顯示)



如果=ISO8859-1,則=ISO8859-1(見(jiàn)表8)。

表8 Compile-charset=Servlet-charset=ISO8859-1時(shí)的變化過(guò)程


序號(hào)步驟說(shuō)明結(jié)果1編寫(xiě)Servlet源文件,且存為GB2312格式D6 D0 CE C4(D6D0=中 CEC4=文)2用Javac-encoding ISO8859-1把Java源文件編譯成Class文件C3 96 C3 90 C3 8E C3 84(UTF)3運(yùn)行時(shí),先從Class文件中用readUTF讀出字符串,在內(nèi)存中的是Unicode編碼00 D6 00 D0 00 CE 00 C4(哈都不是?。。。?根據(jù)Servlet-charset=ISO8859-1把Unicode轉(zhuǎn)化為字節(jié)流D6 D0 CE C45把字節(jié)流輸出到IE中并設(shè)置IE的編碼屬性為Servlet-charset=ISO8859-1D6 D0 CE C46IE用“西歐字符”查看結(jié)果亂碼(原因同表5)7改變IE的頁(yè)面編碼為“簡(jiǎn)體中文”“中文”(正確顯示)



注意:如果不指定Compile-charset或Servlet-charset,其默認(rèn)值均為ISO8859-1。

當(dāng)Compile-charset=Servlet-charset時(shí),第2步和第4步能互逆,“抵消”,顯示結(jié)果均能正確。讀者可試著寫(xiě)一下Compile-charset≠Servlet-charset時(shí)的情況,肯定是不正確的。

當(dāng)輸出對(duì)象是數(shù)據(jù)庫(kù)時(shí)

輸出到數(shù)據(jù)庫(kù)時(shí),原理與輸出到瀏覽器也是一樣的。我們只以Servlet為例,JSP的情況請(qǐng)讀者自行推導(dǎo)(見(jiàn)表9)。

假設(shè)有一個(gè)Servlet,它接收來(lái)自客戶(hù)端(IE,簡(jiǎn)體中文)的漢字字符串,然后把它寫(xiě)入到字符集為ISO8859-1的數(shù)據(jù)庫(kù)中,然后再?gòu)臄?shù)據(jù)庫(kù)中取出這個(gè)字符串,顯示到客戶(hù)端。

前提:客戶(hù)端的字符集是GB2312,數(shù)據(jù)庫(kù)的字符集是ISO8859-1。解釋一下,表中第4、第5步和第15、第16步表示要由編程者來(lái)作轉(zhuǎn)換。第4、5兩步其實(shí)就是一句話(huà):“new String(source.getBytes("ISO8859-1"), DBCharset)”。第15、16兩步也是一句話(huà):“new String(source.getBytes(DBCharset), ClientCharset)”。親愛(ài)的讀者,你在這樣編寫(xiě)代碼時(shí),是否想過(guò)為什么要這么做呢?

序號(hào)步驟說(shuō)明結(jié)果宿主程序1在IE中輸入“中文”D6 D0 CE C4IE2IE把字節(jié)流傳輸?shù)?a title="服務(wù)器" target="_blank" href="http://www.kemok4.com/">服務(wù)器端 3Servlet接受到輸入流,并讀出其中的字符00 D6 00 D0 00 CE 00 C4Servlet4編程者在Servlet中必須把字符串根據(jù)ISO8859-1還原委字節(jié)流,注意,這里一定是ISO8859-1,與客戶(hù)端和數(shù)據(jù)庫(kù)的字符集都無(wú)關(guān)D6 D0 CE C45編程者根據(jù)數(shù)據(jù)庫(kù)字符集ISO8859-1生成新的字符串00 D6 00 D0 00 CE 00 C46把新生成的字符串提交給JDBC00 D6 00 D0 00 CE 00 C4Servlet7JDBC檢測(cè)到數(shù)據(jù)庫(kù)字符集為ISO8859-100 D6 00 D0 00 CE 00 C4JDBC8JDBC把接收到的字符串按照數(shù)據(jù)庫(kù)字符集生成字節(jié)流D6 D0 CE C49JDBC把字節(jié)流寫(xiě)入數(shù)據(jù)庫(kù)中D6 D0 CE C410完成數(shù)據(jù)存儲(chǔ)工作D6 D0 CE C4數(shù)據(jù)庫(kù)截止現(xiàn)在,數(shù)據(jù)入庫(kù)的工作即已完成,用其它的非Java程序讀出的數(shù)據(jù)也是正確的“中文”兩字(字節(jié)流為“C6 Do CE C4”)。以下是從數(shù)據(jù)庫(kù)中取出數(shù)據(jù)的過(guò)程00 D6 00 D0 00 CE 00 C411JDBC從數(shù)據(jù)庫(kù)中取出字節(jié)流D6 D0 CE C4JDBC12JDBC按照數(shù)據(jù)庫(kù)字符集ISO8859-1生成字符串,并提交給Servlet00 D6 00 D0 00 CE 00 C4(Unicode)字節(jié)流13Servlet獲得字符串00 D6 00 D0 00 CE 00 C4(Unicode)Servlet15編程者必須根據(jù)數(shù)據(jù)庫(kù)的字符集ISO8859-1還原成原始字節(jié)流D6 D0 CE C416編程者必須根據(jù)客戶(hù)端字符集GB2312生成新的字符串4E 2D 65 87(Unicode)Servlet準(zhǔn)備把字符串輸出到客戶(hù)端17Servlet根據(jù)生成字節(jié)流。一般說(shuō)來(lái),應(yīng)該與客戶(hù)端字符集一致。本文假定它為GB2312D6 D0 CE C4Servlet18Servlet把字節(jié)流輸出到IE中,如果已指定,還會(huì)設(shè)置IE的編碼為D6 D0 CE C4數(shù)據(jù)庫(kù)19IE根據(jù)指定的編碼和默認(rèn)編碼查看結(jié)果“中文”(正確顯示)IE
結(jié)論及結(jié)束語(yǔ)

行文至此,已可告一段落了。以下給出一個(gè)結(jié)論,作為結(jié)尾。

1.在JSP文件中,要指定contentType。其中,charset的值要與客戶(hù)端瀏覽器所用的字符集一樣;對(duì)于其中的字符串常量,不需做任何處理;對(duì)于字符串變量,要求能根據(jù)ContentType中指定的字符集還原成客戶(hù)端能識(shí)別的字節(jié)流,通俗地說(shuō),就是“字符串變量是基于字符集的”。

2.在Servlet中,必須用HttpServletResponse. setContentType()設(shè)置charset,且設(shè)置成與客戶(hù)端字符集一致;對(duì)于其中的字符串常量,需要在Javac編譯時(shí)指定encoding,這個(gè)encoding必須與編寫(xiě)源文件平臺(tái)的字符集一樣。一般說(shuō)來(lái)都是GB2312或GBK;對(duì)于字符串變量,與JSP一樣。必須“是基于字符集的”。

終點(diǎn)又回到了起點(diǎn),對(duì)于編程者而言,幾乎是什么影響都沒(méi)有。因?yàn)槲覀冊(cè)缇捅桓嬷@么做了。

案例分析


案例:某用戶(hù)在英文Windows上,安裝了外掛的中文平臺(tái),操作系統(tǒng)的字符集是“西歐字符”,對(duì)應(yīng)著ISO8859-1字符集,外掛的中文平臺(tái)是基于Big5碼的。當(dāng)操作者在瀏覽器(默認(rèn)編碼是ISO8859-1)中輸入漢字時(shí),這個(gè)漢字用Big5編碼(在頁(yè)面上無(wú)法正確顯示)。然后,瀏覽器把數(shù)據(jù)提交給服務(wù)器端。同時(shí),有另一個(gè)用戶(hù),在中文版的Windows 2000平臺(tái)上做了同樣的事情。服務(wù)器端程序需要正確處理來(lái)自多種內(nèi)碼的客戶(hù)端的字符串,以便正確地保存到數(shù)據(jù)庫(kù)中。

本案例涉及到多步轉(zhuǎn)換。在第一種客戶(hù)端上:

1. 在客戶(hù)端,Big5內(nèi)碼封裝成ISO8859-1內(nèi)碼;

2. 把封裝后的ISO8859-1字符流傳輸?shù)絁ava程序端;

3. Java程序先是用ISO8859-1識(shí)別輸入流,再用Big5內(nèi)碼來(lái)識(shí)別夾雜在其中的Big5字符;

4.在Java程序中的字符串已經(jīng)是Unicode的了,而且它所代表的圖形符號(hào)與客戶(hù)端的文字所呈現(xiàn)的圖形符號(hào)是完全相同的。

在第二種終端上:

1.客戶(hù)端把GB2312的字符串與其它內(nèi)容一起以GB2312編碼方式傳輸?shù)椒?wù)器端;

2.Java程序先用GB2312內(nèi)碼識(shí)別所有輸入流,再用GB2312內(nèi)碼識(shí)別其中的字符串;

3.Java程序中的Unicode編碼的字符串所代表的圖形符號(hào)與客戶(hù)端字符的圖形符號(hào)是完全相同的。

以上是輸入邏輯。再看輸出邏輯。

有兩個(gè)與數(shù)據(jù)庫(kù)相關(guān)的字符集:一是數(shù)據(jù)庫(kù)真正的字符集,稱(chēng)為DBCharSet;二是數(shù)據(jù)庫(kù)中表現(xiàn)中文的字符集,稱(chēng)為DBChineseCharSet。這一點(diǎn)有些難以理解。請(qǐng)看下述規(guī)則:

1. 與中文相關(guān)的內(nèi)容被按照DBChineseCharSet轉(zhuǎn)化成字節(jié)流A;

2. 把字節(jié)流A和其它非中文的內(nèi)容加在一起,形成新的字節(jié)流B;

3. 數(shù)據(jù)庫(kù)以自己的字符集(DBCharSet)存放字節(jié)流B的所有內(nèi)容。

這種思想類(lèi)似于TCP/IP協(xié)議的層層封裝。

還是看一看具體的例子吧。以第一種客戶(hù)端為例(第二種原理是一樣的)。假定數(shù)據(jù)庫(kù)字符集是ISO8859-1,數(shù)據(jù)庫(kù)中中文字符集為GBK(如圖4):

JSP和Servlet對(duì)中文的處理方式


圖4


圖4所示是從客戶(hù)端接收數(shù)據(jù)然后寫(xiě)到數(shù)據(jù)庫(kù)中的過(guò)程。從數(shù)據(jù)庫(kù)中讀出是其逆過(guò)程,請(qǐng)讀者自行擴(kuò)展到各種情況。

下面給出一段Servlet源程序,僅供參考。其功能是模擬客戶(hù)端輸入,然后寫(xiě)入數(shù)據(jù)庫(kù)中。請(qǐng)讀者自行體會(huì)與上文中的例子“testServlet3.Java”的區(qū)別。

import Java.io.*; import Java.sql.*; public Class testEncode {   public static final String SOURCE="中文";   public static final String CLIENT_ CN_CHARSET ="Big5";   public static final String CLIENT_CHARSET= "ISO8859-1";   public static final String DB_CN_CHARSET="GBK";   public static final String DB_CHARSET="ISO8859-1";   public static void main(String[] args)   {     try     {     System.out.println("SOURCE="+toBytes(SOURCE));      //模擬客戶(hù)端把BIG5轉(zhuǎn)為ISO8859-1    String Source_Iso = new String(SOURCE.getBytes (CLIENT_CN_CHARSET), CLIENT_CHARSET);   System.out.println("Source_Iso="+toBytes(Sour ce_Iso));   //模擬服務(wù)器端接收到字節(jié)流   String Java_Iso = Source_Iso; System.out.println("Java_Iso="+toBytes(Java_Iso));   //模擬JAVA程序先用ISO8859-1識(shí)別,再用BIG5識(shí)別   String Java_Unicode = new String(Java_Iso.getBytes (CLIENT_CHARSET), CLIENT_CN_CHARSET);  System.out.println("Java_Unicode="+toBytes(Java_Unicode));   //模擬JAVA程序根據(jù)GBK生成字節(jié)流,然后再生成ISO8859-1   String DB_Iso = new String(Java_Unicode.getBytes(DB_CN_CHARSET), DB_CHARSET);   System.out.println("DB_Iso="+toBytes(DB_Iso));   DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());   Connection con = DriverManager.getConnection("jdbc:oracle:thin: @172.18.131.206:1521:ora816","scott","tiger");   try   {    Statement stmt = con.createStatement();    stmt.execute("INSERT INTO TEST_TABLE(NAME) VALUES('"+DB_Iso+"')");    stmt.close();   }   finally   {    con.close();   }  }  catch (Exception e)  {   e.printStackTrace();  }     } public static String toBytes(String s) {  if (s == null) return null;   StringBuffer result = new StringBuffer();   for (int i=0; i<s.length(); i++)   {    char c = s.charAt(i);    int intc = (int)c;    result.append(Integer.toHexString(intc));   }   return new String(result);  }     }

用“Javac-encoding gb2312 testEncode.Java”編譯完成后,執(zhí)行之。這里之所以用GB2312進(jìn)行編譯,是因?yàn)樵撐募肬ltraEdit for Windows在GB2312環(huán)境下書(shū)寫(xiě)的。結(jié)果如下:

SOURCE=4e2d6587  //這是用Javac -encoding gb2312編譯的結(jié)果 Source_Iso=a4a4a4e5 //顯示出來(lái)時(shí)把前導(dǎo)的“00”丟掉了,實(shí)際中應(yīng)該有 Java_Iso=a4a4a4e5 //同上 Java_Unicode=4e2d6587 //在Unicode中表示“中文”這兩個(gè)字 DB_Iso=d6d0cec4 //也是在顯示時(shí)把前導(dǎo)“00”丟掉了

OK,檢查一下數(shù)據(jù)庫(kù)中是不是正確存放了用GBK表示的“中文”兩字。打開(kāi)SQLPLUS,輸入如下命令:

SELECT ASCII(SUBSTR(NAME,1,1)),ASCII(SUBSTR(NAME,2,1)), ASCII(SUBSTR(NAME,3,1)), ASCII(SUBSTR(NAME,4,1)) FROM TEST_TABLE;


得到的結(jié)果如下:“214 208 206 196”,正是十六進(jìn)制的“D6 D0 CE C4”。

驗(yàn)證成功!

SetCharacterEncoding和getCharacterEncoding

在Servlet/JSP規(guī)范中,還有兩個(gè)很重要的方法:setCharacterEncoding和getCharacterEncoding。這兩個(gè)方法是在ServletRequest類(lèi)中定義的。顯而易見(jiàn),就是設(shè)置(獲?。┤绾螐腍TTP輸入流中讀取字符的字符集的。從上文可以看出,HTTP在網(wǎng)絡(luò)上傳輸字符串的方式是先把字符串按照某種字符集編碼。然后,把編碼后的字符串按ASCII方式傳輸。

如果這時(shí)直接用諸如getParameter()方法讀取參數(shù),那么得到的就是經(jīng)過(guò)編碼后的字符串,而不是源字符串。通過(guò)setCharacterEncoding設(shè)置正確的字符集后,可以在讀取參數(shù)(getParameter)時(shí),直接把經(jīng)過(guò)編碼后的字符串還原為源字符串。當(dāng)然,這時(shí)的“源字符串”是用Unicode碼表示的。

這兩個(gè)方法給編程帶來(lái)了方便,但是卻不被某些Servlet/JSP引擎支持,如Tomcat 3.2.x。最新的Tomcat 4.0.1和WebLogic Server 6.1支持該方法。

感謝各位的閱讀,以上就是“JSP和Servlet對(duì)中文的處理方式”的內(nèi)容了,經(jīng)過(guò)本文的學(xué)習(xí)后,相信大家對(duì)JSP和Servlet對(duì)中文的處理方式這一問(wèn)題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI