J2EE中的多字節字符處理
開發帶有多字節字符的J2EE應用
作者:Wang Yu
譯者:observer
版權聲明:任何獲得Matrix授權的網站,轉載時請務必以超鏈接形式標明文章原始出處和作者信息及本聲明
作者:Wang Yu;observer
原文地址:http://www.javaworld.com/javaworld/jw-04-2004/jw-0419-multibytes.html
中文地址:http://www.matrix.org.cn/resource/article/43/43954_J2EE_Multibyte_character.html
關鍵詞: J2EE;Multibyte;character
摘要
大多數的J2EE服務器都能很好地支持多字節語言(如中文和日文),但這些J2EE服務器和瀏覽器在支持的方式上是有區別的。當開發者將一些中文(或日文)本地化應用從一種服務器遷移到另一種服務器上時,通常會遇到多字節問題。本文分析了有關多字節字符問題的根源,並給出了一些解決方案和指導原則。作者王越(音)
中文是世界上最複雜、最完備的語言之一。有時我會爲自己是個中國人而感到幸運,特別是當我看到我的一些外國朋友爲學習這門語言(尤其是寫漢字)而絞盡腦汁的時候。可當我用J2EE開發本地化Web應用時,又會感到很不幸。下面我就說說爲什麼。
儘管Java平臺和大多數J2EE服務器都能很好地支持國際化,我在開發中文或日文應用時仍會遇到許多有關多字節字符方面的問題:
·編碼和字符集之間有什麼區別?
·爲什麼多字節字符應用從一種操作系統遷移到另一種上時顯示會有差異?
·爲什麼多字節字符應用從一種應用服務器遷移到另一種上時顯示會有差異?
·爲什麼我的多字節字符應用在IE瀏覽器裏顯示正常,可到了Mozila瀏覽器裏卻又不行?
·爲什麼以UTF-16(通用轉換格式)編碼的應用在大多數J2EE服務器上都不能很好地顯示?
如果你也有同樣的問題,本文將有助於你找到答案。
字符的基礎知識
字符在計算機出現之前就早已存在。大約3,000年前,古代中國就出現了一些特殊的文字符號(即甲骨文)。這些文字符號有特定的形狀和含義,它們中的大部分都有名字和發音。所有這些文字符號彙集成了字碼表,一套從屬於特定語言的獨特字符集合,它們與計算機沒有任何關係。幾千年過去了,許多語言都在發展,數以千計的字符被創造出來。如今我們要將所有這些字符都數字化爲0和1,這樣計算機才能理解它們。
用鍵盤輸入單詞的時候要用到字符輸入法。對於簡單的字符,鍵盤和字符間存在着一對一的映射關係;而對於較複雜的語言,需要多次敲擊鍵盤才能輸入一個字符。
在你能從屏幕上看到字符之前,操作系統必須先將字符存放在內存裏。實際上操作系統在字碼表的字符與一系列非負整數之間定義了一一對應的關係,它們被存放在內存裏並被操作系統調用,這些整數被稱爲字符代碼。
字符可以用文件存儲或通過網絡傳輸。軟件用字符編碼來定義每個字符的字符代碼與八進制序列數之間的對應方法(算法)。有些字符代碼對應一個字節,如ASCII碼;還有一些字符代碼需要對應兩個或更多的字節(如中文和日文),這種對應關係依賴於不同的字符編碼方式。
不同的語言使用不同的字碼表,每個字碼表都有一些特定的編碼方式。在某些情況下,當你選用某種語言時就已經不自覺地選擇了某種字符編碼方式。例如當你選用中文的時候,在默認情況下你用的可能就是GBK中文字碼表及稱作GBK的特定字符編碼方式。
爲了不致混淆,我避免使用字符集這個詞。顯然,字符集與字碼表是同義詞。字符集在HTTP Mime(多用途網際郵件擴充協議)頁頭裏被誤用,其實這裏的“charset(字符集)”是指“encoding(編碼)”。
Java的特徵之一是字符是16位的,這樣就能支持Unicode(一種表示各種語言中許多不同種類的字符的標準方式)。不幸的是,這個特徵在開發多字節J2EE應用中也引發了許多問題,本文將就此進行討論。
開發階段引起的顯示問題
J2EE應用開發包括若干個階段(如圖1所示),每個階段都可能導致多字節字符顯示問題。
圖1 J2EE應用開發生命週期
編碼階段
當你開始J2EE應用編碼時,大多數情況下你會用JBuilder、NetBean之類的IDE,或者是UltraEdit、Vi之類的編輯器。無論你選擇了哪一個,只要在JSP(JavaServer Pages)、Java或HTML文件中有文字字符串,而且這些字符串是像中文或日文這樣的多字節字符,那麼你要是不小心的話就很可能會遇到顯示問題。
文字字符串是存儲於文件中的靜態信息,不同語言的字符采用不同的編碼方式。大多數IDE的默認編碼方式是ISO-8859-1,這種編碼方式適合ASCII字符,但會使多字節字符丟失信息。例如,中文版的NetBean在對文件編碼時其默認編碼方式就不幸爲ISO-8859-1。在我編寫帶有中文字符的JSP文件時(如圖2所示),看上去一切正常。我前面提到,屏幕上顯示的所有字符都在內存中,與編碼方式沒有直接的關係。在保存文件後,如果關閉IDE再重新打開,這些字符就顯示爲亂碼(如圖3所示),這是因爲ISO-8859-1編碼方式在存儲中文字符時會丟失一些信息。
圖2 NetBeans裏的中文字符
圖3 中文字符成了亂碼
字符編碼API
在servlet和JSP規範裏有幾個API用來控制J2EE應用的字符編碼過程。對於servlet請求,setCharacterEncoding()方法對當前HTTP請求設定編碼方式;對於servlet響應,setContentType()方法和setLocale()方法對HTTP響應輸出設置Mime頭的編碼方式。
這些API本身不會引發問題,但如果你忘了用它們就有問題了。例如在有些服務器上你可以正確無誤地顯示多字節字符而不必在代碼中使用上述的任何一個API,但在其它的服務器上運行應用時字符卻變成了亂碼。多字節字符顯示問題的成因在於服務器在處理HTTP請求和響應期間如何對字符進行編碼。以下是服務器確定請求和響應的編碼方式的規則,對大多數服務器都適用:
在處理servlet請求時,服務器按以下次序(自上而下)來確定請求的字符編碼方式:
· 代碼中指定的設置(如setCharacterEncoding()方法中指定的編碼方式)
· 廠商的初始設置
· 默認的設置
在處理servlet響應時,服務器按以下次序(自上而下)來確定響應的字符編碼方式:
· 代碼中指定的設置(如setContentType() 方法和setLocale()方法中指定的編碼方式)
· 廠商的初始設置
· 默認的設置
按照上述規則,如果在代碼中用API進行了指定,所有的服務器都會按指定的字符編碼方式編碼,否則服務器就會各行其道。有些廠商用HTTP表單的隱藏字段(hidden fields)來確定請求的編碼方式,還有些廠商則採用它們自己配置文件中的特定設置。即使是默認設置也不盡相同,大多數廠商採用ISO-8859-1作爲默認設置,還有少數廠商採用操作系統的本地設置值。因此,一些帶有多字節字符的應用在遷移到另一個廠商的J2EE服務器上時就會出現顯示問題。
編譯階段
如果設置正確,在編輯的時候就能在源文件中存儲多字節的文字字符串,但這些源文件不能直接執行。如果編寫的是servlet代碼,這些Java文件在部署到應用服務器之前必須先被編譯成類文件。對於JSP文件,應用服務器在執行前會自動將其編譯成類文件。在編譯階段,字符編碼問題仍有可能存在。爲了運行下面這個簡單示例,請下載本文的源代碼。
程序清單1 EncodingTest.java
1 import java.io.ByteArrayOutputStream;
2 import java.io.OutputStreamWriter;
3
4 public class EncodingTest {
5 public static void main(String[] args) {
6 OutputStreamWriter out = new OutputStreamWriter(new ByteArrayOutputStream());
7 System.out.println("Current Encoding: "+out.getEncoding());
8 System.out.println("Literal output: ÄãºÃ£¡"); // You may not see this Chinese String
9 }
10 }
有關這段源代碼的說明如下:
· 我們用下面的代碼確定系統當前的編碼方式:
6 OutputStreamWriter out = new OutputStreamWriter(new ByteArrayOutputStream());
7 System.out.println("Current Encoding: "+out.getEncoding());
· 第8行包含直接打印輸出中文文字字符串(由於操作系統語言設置的原因可能造成該字符串不能正常顯示)的代碼。
· 用GBK編碼方式保存這個Java源文件。
執行結果如圖4所示。
圖4 示例程序的輸出。
從圖4的執行結果中我們可以歸納出:
· Java編譯器(javac)將系統的語言環境作爲默認的編碼設置,Java運行時(Java Runtime Environment.)也如此。
· 只有第一次的運行結果是正確的,其它的字符串顯示都有問題。
· 僅當運行時的編碼設置與源文件保存時的編碼方式相一致時才能正確顯示多字節文字字符串(否則就必須進行轉碼,參見“運行時階段”部分)。
服務器配置階段
在運行J2EE應用之前,一般會根據特定的需要對應用進行配置。在上一節中,我們發現不同的語言設置會導致文字字符串顯示出問題。實際上配置存在於不同的層面,它們都可能會引發多字節字符問題。
操作系統層
操作系統對語言的支持非常重要。前面提到服務器端對語言的支持會影響JVM默認的編碼設置,而在客戶端的語言支持(如字體)也能直接影響字符的顯示,但這不是本文要討論的重點。
J2EE應用服務器層
大多數服務器都有一個基本服務器設置,可用來配置默認的字符編碼處理方式。清單2就是Tomcat配置文件的一部分(位於$TOMCAT_HOME/conf/web.xml)。
清單2 web.xml
<servlet>
<servlet-name>jsp</servlet-name>
<servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
<init-param>
<param-name>fork</param-name>
<param-value>false</param-value>
</init-param>
<init-param>
<strong>
<param-name>javaEncoding</param-name>>
<param-value>>UTF8</param-value>
</strong>
</init-param>
<load-on-startup>3</load-on-startup>
</servlet>
Tomcat以參數javaEncoding來確定從JSP文件生成Java源文件的Java文件編碼方式。這裏的默認值是UTF-8,這意味着如果JSP文件中的中文字符以GBK編碼保存,將會以UTF-8編碼(瀏覽器端設置)顯示,在這種情況下就可能會出問題。
JVM層
大多數服務器都允許同時運行多個實例,且每個服務器實例都能有自己的JVM實例。此外,還可對每一個JVM實例分別設置。大多數服務器用本地設置來爲每個實例定義默認的語言支持。
圖5 Sun ONE 應用服務器設置
圖5顯示的是Sun ONE(開放網絡環境)應用服務器的一個本地單個實例設置。該設置給出了登錄系統和標準輸出的默認字符編碼方式。
此外,不同的服務器使用的JVM版本可能會不同,而不同的JDK版本支持的編碼標準各異,所有這些都會導致遷移問題。例如Sun ONE應用服務器與Tomcat都支持J2SE 1.4,而有些服務器只支持到J2SE 1.3。J2SE 1.4支持Unicode 3.1,它具有許多早期版本所沒有的新特性。
單個應用層
每個部署在服務器上的應用在運行前都可以爲其配置獨立的編碼設置,這就使得在同一個服務器實例上能夠運行多個採用不同語言的應用。一些服務器用以下的字符編碼設置爲每個部署的應用指定其應使用的編碼方式:
<locale-charset-info default-locale="en_US">
</locale-charset-map locale="zh_CN" agent="Mozilla/4.77 [en] (Windows NT 5.0; U)" charset="GBK">
</locale-charset-info>
這種分層配置的目的是爲了靈活性和可維護性。但不幸的是,當在服務器間遷移時這種做法就可能導致出問題,因爲並非所有的服務器配置都遵循標準。比如說,如果在一個支持本地字符集設置的服務器上開發了應用,那麼當把該應用遷移到另一個不支持這種編碼設置的服務器上時就可能會遇到問題。
運行時階段
在運行過程中J2EE應用很可能會與其它外部系統通信。應用也許會讀寫文件,或者用數據庫管理數據,有時候還可能用LDAP(輕量目錄訪問協議)服務器存儲標識信息。在這些情況下,J2EE應用和外部系統之間需要進行數據交換。如果數據中帶有象中文這樣的多字節字符,就可能會遇到問題。
大部分的外部系統都有他們自己的編碼設置。例如LDAP服務器很可能使用UTF-8對字符編碼;Oracle數據庫系統用環境變量NLS_LANG來指定編碼方式。如果Oracle是安裝在中文操作系統上,該變量的默認設置爲ZHS16GBK,也就是用GBK編碼方式來存儲中文字符。因此當J2EE應用的編碼設置與外部系統不同時需要進行轉碼,通常用以下代碼來完成這一工作:
byte[] defaultBytes = original.getBytes(current_encoding);
String newEncodingStr = new String(defaultBytes, old_encoding);
以上代碼給出瞭如何將字符串從一種編碼方式轉換爲另一種。例如你在LDAP服務器中用UTF-8編碼存儲了一個用戶名(多字節字符),而在J2EE應用中用的卻是GBK編碼,因此當應用從LDAP服務器中取用戶名時就可能被錯誤地編碼。要解決這個問題,可以用original.getBytes("GBK")得到原始的字節,然後用new String(defaultBytes, "UTF-8")構造一個新字符串,這樣就可以正確顯示了。
客戶端顯示階段
現在大多數J2EE應用都採用瀏覽器/服務器架構,以瀏覽器作爲客戶端。要在瀏覽器里正確顯示多字節字符,需要注意以下幾個方面:
瀏覽器語言支持:
爲能正確地顯示多字節字符,瀏覽器及其所運行的操作系統應提供對特定語言的支持,比如字體和字碼表。
瀏覽器編碼設置
服務器返回的HTML頭(<meta http-equiv="content-type" content="text/html;charset=gb2312">)向瀏覽器聲明瞭該頁面使用的編碼方式,否則瀏覽器將使用默認編碼設置或自動進行匹配。當然,用戶也可以對頁面的編碼進行設定,如圖6所示。
圖6 Netscape的編碼設置頁
因此如果頁面沒有聲明,多字節字符就可能顯示不正確,在這種情況下用戶必須手工設定當前頁面的編碼方式。
HTTP POST編碼
用HTML頁面的Form標籤向服務器提交數據會使情況變得更爲複雜。瀏覽器的編碼方式取決於當前頁面的編碼設定,對Form標籤也照此處理。這意味着如果ASCII格式的HTML頁面用ISO-8859-1編碼,那麼用戶在此頁面中將不能提交中文字符。這是因爲所有提交的數據都用ISO-8859-1編碼,這將使中文字符丟失字節。所有的瀏覽器都遵守這個HTML標準。
HTTP GET編碼
URL鏈接中帶有多字節字符會使事情複雜化,像<A href = getuser.jsp?name=**>View detail information of this user</A>(**代表多字節字符)。這種情況很常見,例如在鏈接里加入用戶名或其它信息以便傳給下一頁。但RFC (因特網標準草案) 2396中並未明確規定URL中有非US-ASCII字符時的格式,不同的瀏覽器會採用它們自己的方式來編碼URL中的多字節字符。
以Mozila爲例(如圖7/8/9/10),通常是在HTTP請求發送前對URL編碼。我們知道在URL編碼過程中,首先根據某種編碼方式(如UTF-8或GBK)將一個多字節字符轉換成兩個或更多的字節,然後每個字節用3個字符組成的字符串%xy來表示,其中xy是表示該字節的兩個十六進制數。這方面的更多信息可參考HTML規範。不管怎樣,URL編碼所採用的編碼方式取決於當前頁面的編碼方式。
我用下面這個gbk_test.jsp頁面做演示:
清單3 gbk_test.jsp
<%@page contentType="text/html;charset=GBK"%>
<HTML>
<BODY>
<a href='/chartest/servlet/httpGetTest?name=王'><h1>Test for GBK encoded URL</h1></a>
</BODY>
</HTML>
x738b是一箇中文字符的轉義值,這個中文字符就是我的姓。該頁面如圖7所示。
圖7 Mozilla的URL
當鼠標移動到該鏈接上時,鏈接的地址就會在狀態欄中顯示出來,可以看到URL中嵌入了一箇中文字符。點擊頁面中的鏈接,可以從地址欄中清楚地看到該字符已被URL編碼。字符x738b被編碼爲%CD%F5,這是URL編碼與GBK編碼共同作用的結果。在服務器端,用request.getQueryString()方法取出查詢字符串;爲與查詢字符串相比較,接下來的一行用另一種方法getParameter(String)來顯示字符,如圖8所示。
圖8 Mozilla中的URL編碼
把當前頁面的編碼方式由GBK改爲UTF-8,再次點擊頁面中的鏈接,出現的結果爲:x738b被編碼爲%E7%8E%8B,如圖9所示,這是URL編碼與UTF-8共同作用的結果。
圖9 Mozilla中的URL編碼
Microsoft的IE瀏覽器卻以不同的方式處理多字節的URL編碼。IE在HTTP請求發送前不對URL編碼,URL編碼方式取決於當前頁面的編碼方式,如圖10所示。
圖10 IE不對URL編碼
IE還有一個高級選項設置,可以強制瀏覽器總是以UTF-8編碼方式發送URL請求,如圖11所示。
圖11 IE中的高級選項設置
根據以上說明我們會面臨一個問題:如果應用頁面的URL鏈接中帶有多字節字符,只要用GBK編碼就能在Mozilla里正常使用;但如果用戶的客戶端是IE,且在設置中強制瀏覽器以UTF-8編碼發送URL請求,那麼在使用中就會遇到問題。
多字節字符問題的解決方案
編寫能運行於任何服務器、在任何瀏覽器中都能正常顯示的J2EE應用是個挑戰,下面是一些針對J2EE應用多字節字符問題的解決方案:
通用原則:從不假定客戶端(瀏覽器)和服務器端有任何默認設置。
& 在編輯階段,不要假定IDE的默認編碼設置是你想要的,要手工設置它們。
如果IDE不支持特定語言,就在Java代碼中用/uXXXX轉義序列,在HTML頁面中用&#XXXX轉義序列,或用隨JDK分發的native2ascii工具將本地文字字符串轉換成Unicode轉義序列,這樣就能避免絕大部分問題。
& 在編碼階段,從不假定服務器默認的編碼處理設置是正確的,而用下面的方法顯式指定:
· 請求:setCharacterEncoding()
· 響應:setContentType(), setLocale(), <%@ page contentType="text/html; charset=encoding" %>
在爲多種語言開發應用時,採用UTF-8編碼方式或將所有語言的字符都用/uXXXX轉義序列表示。
· 在編譯Java類時,確保當前語言環境變量與編碼方式正確匹配。
· 在配置階段,儘可能地使用標準設置。例如在Servlet 2.4規範中有一個配置每個應用的字符編碼方式的標準:
<locale-encoding-mapping-list>
<locale-encoding-mapping>
<locale>ja</locale>
<encoding>Shift_JIS</encoding>
</locale-encoding-mapping>
</locale-encoding-mapping-list>
當與外部系統通信時,儘可能地找出這些系統的編碼方式,如果編碼不同就進行轉碼。可以用UnicodeFormatter.java作爲調試器打印所有的字節:
清單4 UnicodeFormatter.java
import java.io.*;
public class UnicodeFormatter {
static public String byteToHex(byte b) {
// Returns hex String representation of byte b
char hexDigit[] = {
'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
};
char[] array = { hexDigit[(b >> 4) & 0x0f], hexDigit[b & 0x0f] };
return new String(array);
}
static public String charToHex(char c) {
// Returns hex String representation of char c
byte hi = (byte) (c >>> 8);
byte lo = (byte) (c & 0xff);
return byteToHex(hi) + byteToHex(lo);
}
}
總在HTML頁面中對編碼方式做顯式聲明,例如<meta http-equiv="content-type" content="text/html;charset=gb2312">,不要假定瀏覽器的默認設置是正確的。
· 不要在鏈接里加入多字節字符,例如查詢字符串裏不要用用戶名,改用用戶ID。
· 如果必須在鏈接里加入多字節字符,那就要對URL進行手工編碼,可以在服務器端處理(用Java),也可以在客戶端處理(用JavaScript或VBscript)。
難題之一:UTF-16
有了前面的知識,現在我們來分析一個真正的難題,這是我負責的一個ISV(獨立軟件開發商)在項目中遇到的:J2EE中的UTF-16。
現行的中文字符標準(GB18030)定義並支持27,484箇中文字符。儘管這個數字看起來很大,實際上對中國人來說還不夠。目前中文擁有60,000多個字符,每年還在快速增長,這個狀況對中國政府在信息化方面的工作會產生嚴重影響。例如我姐姐的名在標準字符集中就沒有,因此銀行或郵電系統的計算機就打不出她的名。
我的ISV希望能建立一套完整的、能讓所有人滿意的中文字符系統。它定義了自己的字碼表,有兩個現成的字符字碼表可供選擇:採用GB18030標準,可擴充到1600萬個字符;或採用Unicode 3.1標準,可支持1,112,064個字符。GB18030標準定義了編碼規則,也叫做GB18030,它用起來很簡便,目前已被JDK支持。然而如果採用Unicode 3.1標準,我們就可以從三種編碼方式中進行選擇:UTF-8、UTF-16或UTF-32。
我的ISV希望用UTF-16編碼來處理它對中文字符的Unicode擴展。UTF-16編碼最主要的特點就是所有的ASCII字符都被編碼爲16位的單元,這會在各個階段引發問題。在幾個服務器上做過測試後,ISV發現J2EE應用根本不支持UTF-16編碼。果真如此嗎?我們一起來分析開發的各個階段以找出問題所在。
編輯階段
如果在Java、JSP或HTML源文件中有多字節文字字符串,就需要用支持它們的IDE。我用的是NetBeans,只要將文本編碼屬性設置爲UTF-16就能輕鬆支持UTF-16編碼。圖12所示的是一個用UTF-16編碼的JSP頁面,它裏面只有一個靜態文字字符串“hello world!”。該頁面在Tomcat上運行,在Mozilla中顯示。
圖12 Mozilla中用UTF-16編碼的頁面
編譯階段
由於在Java或JSP源文件中帶有用UTF-16編碼的字符,因此需要編譯器的支持。可以用javac -encoding UTF-16命令來編譯Java源文件,而在NetBeans裏則可通過GUI方便地設置編譯器的屬性。通過一些簡單的代碼測試可以發現:如果servlet文件中的字符是用UTF-16編碼的,那麼運行時就不會有問題。
運行時動態編譯的JSP文件值得我們注意。幸運的是,大多數服務器可以對JSP頁面的編碼方式進行配置;而不幸的是,在Tomcat和Sun ONE應用服務器上做測試時,我發現用來將JSP文件轉換爲servlet Java源文件的Jasper不能識別被UTF-16編碼過的JSP標籤(比如<%page..%>),所有這些標籤都被當作文字字符串處理了!我認爲問題的根源可能在於Jasper(大多數應用服務器都用它做JSP編譯器),因爲它以字節爲單位來識別JSP的特定記號和標籤。
瀏覽器測試
現在我們看到由於識別被UTF-16編碼過的JSP標籤失敗,JSP不能支持UTF-16編碼的文字字符,而servlets卻沒有這個問題。
且慢!爲使測試更能說明問題,我們在測試代碼中加入POST功能,讓用戶通過HTML的Form標籤提交UTF-16編碼的字符。從本文的資源一節中下載下面的示例程序:servlet PostForm.java和servlet ByteTest.java。Servlet PostForm.java用來輸出一個用UTF-16編碼的頁面,它有一個用來向服務器提交數據的表單。在ByteTest.java裏,由於不能確定服務器是否配置爲UTF-16編碼方式,我沒有用request.getParameter()方法顯示瀏覽器提交的數據,而改用request.getInputStream()方法從請求中提取原始數據,然後打印從瀏覽器得到的每一個字節。
清單5 PostForm.java
public class PostForm extends HttpServlet {
....
protected void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-16");
PrintWriter out = response.getWriter();
out.println("<html><head>");
out.println("<meta content="text/html; charset=UTF-16/" http-equiv="content-type/">");
out.println("</head><body>");
out.println("<form action=/"servlet/ByteTest/" method=/"POST/">");
out.println("<input type=/"text/" name=/"name/"><input type=/"submit/">");
out.println("</form></body></html>");
out.close();
}
....
}
清單6 ByteTest.java
public class ByteTest extends HttpServlet {
...
protected void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
ServletInputStream in = request.getInputStream();
response.setContentType("text/html");
PrintWriter out = response.getWriter();
byte[] postdata = new byte[50];
int size = in.read(postdata,0,50);
in.close();
out.println("<html>");
out.println("<head>");
out.println("<title>Servlet</title>");
out.println("</head>");
out.println("<body>");
printBytes(out,postdata, size, "postdata");
out.println("</body>");
out.println("</html>");
out.close();
}
...
}
在運行過程中PostForm頁面顯然會用UTF-16編碼,而ByteTest的輸出結果又會是什麼呢?
· IE:儘管頁面是用UTF-16編碼的,瀏覽器對所有輸入的字符都採用UTF-8編碼。
· Mozilla:無論在這個UTF-16編碼的頁面裏輸入什麼字符,只有“=”這個字符能顯示出來,這個運行結果顯然是錯誤的。
結論
J2EE應用只能在以下條件下使用UTF-16編碼:
· 只用於servlet編程
· 瀏覽器只限制於用IE
· 雖然瀏覽器端的頁面用UTF-16編碼,在服務器端要用UTF-8解碼
實際上,在J2EE應用中使用UTF-8編碼並不困難。在Unicode 3.1標準中,UTF-8編碼與UTF-16編碼能處理的字符數目是一樣的,只是在存儲和處理效率方面有差異。
結束語
由此可見,如果J2EE應用遇到了多字節字符問題,你一定要深入到開發生命週期的各個階段,檢查服務器和客戶端的配置情況,並藉助調試工具,這樣才能找出問題的根源所在。
關於作者
王越現在是Sun公司的Java技術工程師和技術架構諮詢師,負責本地ISV的技術支持,以及J2EE、EJB (企業JavaBeans)、JSP/Servlet、JMS(Java消息服務)和Web services等主要Java技術的宣傳和諮詢工作。
資源
·javaworld.com:javaworld.com
·Matrix-Java開發者社區:http://www.matrix.org.cn/
·下載本文的源代碼:http://javaworld.com/javaworld/jw-04-2004/multibytes/jw-0419-multibytes.zip
·UnicodeFormatter.java: http://java.sun.com/docs/books/tutorial/i18n/text/example-1dot1/UnicodeFormatter.java
·最新的HTML規範:http://www.w3.org/TR/html4/
·有關字符代碼問題的一個演示:http://www.cs.tut.fi/~jkorpela/chars.html